diff --git a/Charts/Charts.xcodeproj/project.pbxproj b/Charts/Charts.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..0e54da9c3e --- /dev/null +++ b/Charts/Charts.xcodeproj/project.pbxproj @@ -0,0 +1,626 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5B4BCD3E1AA9C0A60063F019 /* ChartFillFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4BCD3D1AA9C0A60063F019 /* ChartFillFormatter.swift */; }; + 5B4BCD401AA9C4930063F019 /* ChartTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4BCD3F1AA9C4930063F019 /* ChartTransformer.swift */; }; + 5B6556F71AB72BA000FFBFD3 /* ChartComponentBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6556F61AB72BA000FFBFD3 /* ChartComponentBase.swift */; }; + 5B680D1D1A9D16F90026A057 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B680D1C1A9D16F90026A057 /* UIKit.framework */; }; + 5B680D1F1A9D17C30026A057 /* ChartAxisBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6A1A9D151C00CE82E1 /* ChartAxisBase.swift */; }; + 5B680D201A9D17C30026A057 /* ChartLegend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6B1A9D151C00CE82E1 /* ChartLegend.swift */; }; + 5B680D211A9D17C30026A057 /* ChartLimitLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6C1A9D151C00CE82E1 /* ChartLimitLine.swift */; }; + 5B680D221A9D17C30026A057 /* ChartXAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6D1A9D151C00CE82E1 /* ChartXAxis.swift */; }; + 5B680D231A9D17C30026A057 /* ChartYAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6E1A9D151C00CE82E1 /* ChartYAxis.swift */; }; + 5B680D271A9D17C30026A057 /* ChartColorTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC791A9D151C00CE82E1 /* ChartColorTemplates.swift */; }; + 5B680D281A9D17C30026A057 /* ChartHighlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC7A1A9D151C00CE82E1 /* ChartHighlight.swift */; }; + 5B680D291A9D17C30026A057 /* ChartSelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC7B1A9D151C00CE82E1 /* ChartSelInfo.swift */; }; + 5B680D2A1A9D17C30026A057 /* ChartUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC7C1A9D151C00CE82E1 /* ChartUtils.swift */; }; + 5B680D3D1A9D1AD90026A057 /* Charts.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B680D3C1A9D1AD90026A057 /* Charts.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5B6A546B1AA5C23F000F57C2 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A546A1AA5C23F000F57C2 /* ChartMarker.swift */; }; + 5B6A546E1AA5D2DC000F57C2 /* ChartAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A546D1AA5D2DC000F57C2 /* ChartAnimator.swift */; }; + 5B6A54701AA5DB34000F57C2 /* ChartRendererBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A546F1AA5DB34000F57C2 /* ChartRendererBase.swift */; }; + 5B6A54741AA5DEDC000F57C2 /* ChartXAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54731AA5DEDC000F57C2 /* ChartXAxisRenderer.swift */; }; + 5B6A54761AA5DEE3000F57C2 /* ChartXAxisRendererBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54751AA5DEE3000F57C2 /* ChartXAxisRendererBarChart.swift */; }; + 5B6A54781AA5DEF0000F57C2 /* ChartXAxisRendererRadarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54771AA5DEF0000F57C2 /* ChartXAxisRendererRadarChart.swift */; }; + 5B6A547C1AA5DF02000F57C2 /* ChartXAxisRendererHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A547B1AA5DF02000F57C2 /* ChartXAxisRendererHorizontalBarChart.swift */; }; + 5B6A547E1AA5DF1A000F57C2 /* ChartYAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A547D1AA5DF1A000F57C2 /* ChartYAxisRenderer.swift */; }; + 5B6A54801AA5DF28000F57C2 /* ChartYAxisRendererHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A547F1AA5DF28000F57C2 /* ChartYAxisRendererHorizontalBarChart.swift */; }; + 5B6A54821AA5DF34000F57C2 /* ChartYAxisRendererRadarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54811AA5DF34000F57C2 /* ChartYAxisRendererRadarChart.swift */; }; + 5B6A54851AA669C9000F57C2 /* ScatterChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54841AA669C9000F57C2 /* ScatterChartRenderer.swift */; }; + 5B6A54871AA669F4000F57C2 /* RadarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54861AA669F4000F57C2 /* RadarChartRenderer.swift */; }; + 5B6A54891AA66A1A000F57C2 /* PieChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54881AA66A1A000F57C2 /* PieChartRenderer.swift */; }; + 5B6A548B1AA66A3D000F57C2 /* LineChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A548A1AA66A3D000F57C2 /* LineChartRenderer.swift */; }; + 5B6A548D1AA66A60000F57C2 /* ChartLegendRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A548C1AA66A60000F57C2 /* ChartLegendRenderer.swift */; }; + 5B6A548F1AA66A7A000F57C2 /* HorizontalBarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A548E1AA66A7A000F57C2 /* HorizontalBarChartRenderer.swift */; }; + 5B6A54911AA66A8D000F57C2 /* ChartDataRendererBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54901AA66A8D000F57C2 /* ChartDataRendererBase.swift */; }; + 5B6A54931AA66AAB000F57C2 /* CombinedChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54921AA66AAB000F57C2 /* CombinedChartRenderer.swift */; }; + 5B6A54951AA66AC0000F57C2 /* CandleStickChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54941AA66AC0000F57C2 /* CandleStickChartRenderer.swift */; }; + 5B6A54971AA66AD2000F57C2 /* BarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54961AA66AD2000F57C2 /* BarChartRenderer.swift */; }; + 5B6A54991AA66B14000F57C2 /* BarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54981AA66B14000F57C2 /* BarChartView.swift */; }; + 5B6A549B1AA66B2C000F57C2 /* BarLineChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A549A1AA66B2C000F57C2 /* BarLineChartViewBase.swift */; }; + 5B6A549D1AA66B3C000F57C2 /* CandleStickChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A549C1AA66B3C000F57C2 /* CandleStickChartView.swift */; }; + 5B6A549F1AA66B59000F57C2 /* CombinedChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A549E1AA66B59000F57C2 /* CombinedChartView.swift */; }; + 5B6A54A31AA66B7C000F57C2 /* LineChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54A21AA66B7C000F57C2 /* LineChartView.swift */; }; + 5B6A54A51AA66B92000F57C2 /* PieChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54A41AA66B92000F57C2 /* PieChartView.swift */; }; + 5B6A54A71AA66BA7000F57C2 /* PieRadarChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54A61AA66BA7000F57C2 /* PieRadarChartViewBase.swift */; }; + 5B6A54A91AA66BBA000F57C2 /* RadarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54A81AA66BBA000F57C2 /* RadarChartView.swift */; }; + 5B6A54AB1AA66BC8000F57C2 /* ScatterChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54AA1AA66BC8000F57C2 /* ScatterChartView.swift */; }; + 5B6A54AC1AA66C1E000F57C2 /* ChartAxisRendererBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54711AA5DCA8000F57C2 /* ChartAxisRendererBase.swift */; }; + 5B6A54CC1AA74516000F57C2 /* BarChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54B71AA74516000F57C2 /* BarChartData.swift */; }; + 5B6A54CD1AA74516000F57C2 /* BarChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54B81AA74516000F57C2 /* BarChartDataEntry.swift */; }; + 5B6A54CE1AA74516000F57C2 /* BarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54B91AA74516000F57C2 /* BarChartDataSet.swift */; }; + 5B6A54CF1AA74516000F57C2 /* BarLineScatterCandleChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54BA1AA74516000F57C2 /* BarLineScatterCandleChartData.swift */; }; + 5B6A54D01AA74516000F57C2 /* BarLineScatterCandleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54BB1AA74516000F57C2 /* BarLineScatterCandleChartDataSet.swift */; }; + 5B6A54D11AA74516000F57C2 /* CandleChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54BC1AA74516000F57C2 /* CandleChartData.swift */; }; + 5B6A54D21AA74516000F57C2 /* CandleChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54BD1AA74516000F57C2 /* CandleChartDataEntry.swift */; }; + 5B6A54D31AA74516000F57C2 /* CandleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54BE1AA74516000F57C2 /* CandleChartDataSet.swift */; }; + 5B6A54D41AA74516000F57C2 /* CombinedChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54BF1AA74516000F57C2 /* CombinedChartData.swift */; }; + 5B6A54D51AA74516000F57C2 /* ChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C01AA74516000F57C2 /* ChartData.swift */; }; + 5B6A54D61AA74516000F57C2 /* ChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C11AA74516000F57C2 /* ChartDataEntry.swift */; }; + 5B6A54D71AA74516000F57C2 /* ChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C21AA74516000F57C2 /* ChartDataSet.swift */; }; + 5B6A54D81AA74516000F57C2 /* LineChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C31AA74516000F57C2 /* LineChartData.swift */; }; + 5B6A54D91AA74516000F57C2 /* LineChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C41AA74516000F57C2 /* LineChartDataSet.swift */; }; + 5B6A54DA1AA74516000F57C2 /* LineRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C51AA74516000F57C2 /* LineRadarChartDataSet.swift */; }; + 5B6A54DB1AA74516000F57C2 /* PieChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C61AA74516000F57C2 /* PieChartData.swift */; }; + 5B6A54DC1AA74516000F57C2 /* PieChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C71AA74516000F57C2 /* PieChartDataSet.swift */; }; + 5B6A54DD1AA74516000F57C2 /* RadarChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C81AA74516000F57C2 /* RadarChartData.swift */; }; + 5B6A54DE1AA74516000F57C2 /* RadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54C91AA74516000F57C2 /* RadarChartDataSet.swift */; }; + 5B6A54DF1AA74516000F57C2 /* ScatterChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54CA1AA74516000F57C2 /* ScatterChartData.swift */; }; + 5B6A54E01AA74516000F57C2 /* ScatterChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54CB1AA74516000F57C2 /* ScatterChartDataSet.swift */; }; + 5BA8EC7D1A9D151C00CE82E1 /* ChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC671A9D151C00CE82E1 /* ChartViewBase.swift */; }; + 5BA8EC881A9D151C00CE82E1 /* ChartDataApproximatorFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC751A9D151C00CE82E1 /* ChartDataApproximatorFilter.swift */; }; + 5BA8EC891A9D151C00CE82E1 /* ChartDataBaseFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC761A9D151C00CE82E1 /* ChartDataBaseFilter.swift */; }; + 5BD8F06D1AB897D500566E05 /* ChartViewPortHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD8F06C1AB897D500566E05 /* ChartViewPortHandler.swift */; }; + 5BD8F06E1AB89AD800566E05 /* HorizontalBarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6A54A01AA66B6A000F57C2 /* HorizontalBarChartView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5B4BCD3D1AA9C0A60063F019 /* ChartFillFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartFillFormatter.swift; sourceTree = ""; }; + 5B4BCD3F1AA9C4930063F019 /* ChartTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartTransformer.swift; sourceTree = ""; }; + 5B6556F61AB72BA000FFBFD3 /* ChartComponentBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartComponentBase.swift; sourceTree = ""; }; + 5B680D1C1A9D16F90026A057 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 5B680D3C1A9D1AD90026A057 /* Charts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Charts.h; sourceTree = ""; }; + 5B6A546A1AA5C23F000F57C2 /* ChartMarker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = ""; }; + 5B6A546D1AA5D2DC000F57C2 /* ChartAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartAnimator.swift; sourceTree = ""; }; + 5B6A546F1AA5DB34000F57C2 /* ChartRendererBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartRendererBase.swift; sourceTree = ""; }; + 5B6A54711AA5DCA8000F57C2 /* ChartAxisRendererBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartAxisRendererBase.swift; sourceTree = ""; }; + 5B6A54731AA5DEDC000F57C2 /* ChartXAxisRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartXAxisRenderer.swift; sourceTree = ""; }; + 5B6A54751AA5DEE3000F57C2 /* ChartXAxisRendererBarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartXAxisRendererBarChart.swift; sourceTree = ""; }; + 5B6A54771AA5DEF0000F57C2 /* ChartXAxisRendererRadarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartXAxisRendererRadarChart.swift; sourceTree = ""; }; + 5B6A547B1AA5DF02000F57C2 /* ChartXAxisRendererHorizontalBarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartXAxisRendererHorizontalBarChart.swift; sourceTree = ""; }; + 5B6A547D1AA5DF1A000F57C2 /* ChartYAxisRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartYAxisRenderer.swift; sourceTree = ""; }; + 5B6A547F1AA5DF28000F57C2 /* ChartYAxisRendererHorizontalBarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartYAxisRendererHorizontalBarChart.swift; sourceTree = ""; }; + 5B6A54811AA5DF34000F57C2 /* ChartYAxisRendererRadarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartYAxisRendererRadarChart.swift; sourceTree = ""; }; + 5B6A54841AA669C9000F57C2 /* ScatterChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScatterChartRenderer.swift; sourceTree = ""; }; + 5B6A54861AA669F4000F57C2 /* RadarChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadarChartRenderer.swift; sourceTree = ""; }; + 5B6A54881AA66A1A000F57C2 /* PieChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartRenderer.swift; sourceTree = ""; }; + 5B6A548A1AA66A3D000F57C2 /* LineChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineChartRenderer.swift; sourceTree = ""; }; + 5B6A548C1AA66A60000F57C2 /* ChartLegendRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartLegendRenderer.swift; sourceTree = ""; }; + 5B6A548E1AA66A7A000F57C2 /* HorizontalBarChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalBarChartRenderer.swift; sourceTree = ""; }; + 5B6A54901AA66A8D000F57C2 /* ChartDataRendererBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataRendererBase.swift; sourceTree = ""; }; + 5B6A54921AA66AAB000F57C2 /* CombinedChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinedChartRenderer.swift; sourceTree = ""; }; + 5B6A54941AA66AC0000F57C2 /* CandleStickChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandleStickChartRenderer.swift; sourceTree = ""; }; + 5B6A54961AA66AD2000F57C2 /* BarChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartRenderer.swift; sourceTree = ""; }; + 5B6A54981AA66B14000F57C2 /* BarChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartView.swift; sourceTree = ""; }; + 5B6A549A1AA66B2C000F57C2 /* BarLineChartViewBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BarLineChartViewBase.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 5B6A549C1AA66B3C000F57C2 /* CandleStickChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandleStickChartView.swift; sourceTree = ""; }; + 5B6A549E1AA66B59000F57C2 /* CombinedChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinedChartView.swift; sourceTree = ""; }; + 5B6A54A01AA66B6A000F57C2 /* HorizontalBarChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalBarChartView.swift; sourceTree = ""; }; + 5B6A54A21AA66B7C000F57C2 /* LineChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineChartView.swift; sourceTree = ""; }; + 5B6A54A41AA66B92000F57C2 /* PieChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartView.swift; sourceTree = ""; }; + 5B6A54A61AA66BA7000F57C2 /* PieRadarChartViewBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieRadarChartViewBase.swift; sourceTree = ""; }; + 5B6A54A81AA66BBA000F57C2 /* RadarChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadarChartView.swift; sourceTree = ""; }; + 5B6A54AA1AA66BC8000F57C2 /* ScatterChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScatterChartView.swift; sourceTree = ""; }; + 5B6A54B71AA74516000F57C2 /* BarChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartData.swift; sourceTree = ""; }; + 5B6A54B81AA74516000F57C2 /* BarChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartDataEntry.swift; sourceTree = ""; }; + 5B6A54B91AA74516000F57C2 /* BarChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartDataSet.swift; sourceTree = ""; }; + 5B6A54BA1AA74516000F57C2 /* BarLineScatterCandleChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarLineScatterCandleChartData.swift; sourceTree = ""; }; + 5B6A54BB1AA74516000F57C2 /* BarLineScatterCandleChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarLineScatterCandleChartDataSet.swift; sourceTree = ""; }; + 5B6A54BC1AA74516000F57C2 /* CandleChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandleChartData.swift; sourceTree = ""; }; + 5B6A54BD1AA74516000F57C2 /* CandleChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandleChartDataEntry.swift; sourceTree = ""; }; + 5B6A54BE1AA74516000F57C2 /* CandleChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CandleChartDataSet.swift; sourceTree = ""; }; + 5B6A54BF1AA74516000F57C2 /* CombinedChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinedChartData.swift; sourceTree = ""; }; + 5B6A54C01AA74516000F57C2 /* ChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartData.swift; sourceTree = ""; }; + 5B6A54C11AA74516000F57C2 /* ChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataEntry.swift; sourceTree = ""; }; + 5B6A54C21AA74516000F57C2 /* ChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataSet.swift; sourceTree = ""; }; + 5B6A54C31AA74516000F57C2 /* LineChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineChartData.swift; sourceTree = ""; }; + 5B6A54C41AA74516000F57C2 /* LineChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineChartDataSet.swift; sourceTree = ""; }; + 5B6A54C51AA74516000F57C2 /* LineRadarChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineRadarChartDataSet.swift; sourceTree = ""; }; + 5B6A54C61AA74516000F57C2 /* PieChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartData.swift; sourceTree = ""; }; + 5B6A54C71AA74516000F57C2 /* PieChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieChartDataSet.swift; sourceTree = ""; }; + 5B6A54C81AA74516000F57C2 /* RadarChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadarChartData.swift; sourceTree = ""; }; + 5B6A54C91AA74516000F57C2 /* RadarChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadarChartDataSet.swift; sourceTree = ""; }; + 5B6A54CA1AA74516000F57C2 /* ScatterChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScatterChartData.swift; sourceTree = ""; }; + 5B6A54CB1AA74516000F57C2 /* ScatterChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScatterChartDataSet.swift; sourceTree = ""; }; + 5BA8EC401A9D14DC00CE82E1 /* Charts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Charts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5BA8EC441A9D14DC00CE82E1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5BA8EC671A9D151C00CE82E1 /* ChartViewBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartViewBase.swift; sourceTree = ""; }; + 5BA8EC6A1A9D151C00CE82E1 /* ChartAxisBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChartAxisBase.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + 5BA8EC6B1A9D151C00CE82E1 /* ChartLegend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartLegend.swift; sourceTree = ""; }; + 5BA8EC6C1A9D151C00CE82E1 /* ChartLimitLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartLimitLine.swift; sourceTree = ""; }; + 5BA8EC6D1A9D151C00CE82E1 /* ChartXAxis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartXAxis.swift; sourceTree = ""; }; + 5BA8EC6E1A9D151C00CE82E1 /* ChartYAxis.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartYAxis.swift; sourceTree = ""; }; + 5BA8EC751A9D151C00CE82E1 /* ChartDataApproximatorFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataApproximatorFilter.swift; sourceTree = ""; }; + 5BA8EC761A9D151C00CE82E1 /* ChartDataBaseFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataBaseFilter.swift; sourceTree = ""; }; + 5BA8EC791A9D151C00CE82E1 /* ChartColorTemplates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartColorTemplates.swift; sourceTree = ""; }; + 5BA8EC7A1A9D151C00CE82E1 /* ChartHighlight.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartHighlight.swift; sourceTree = ""; }; + 5BA8EC7B1A9D151C00CE82E1 /* ChartSelInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartSelInfo.swift; sourceTree = ""; }; + 5BA8EC7C1A9D151C00CE82E1 /* ChartUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartUtils.swift; sourceTree = ""; }; + 5BD8F06C1AB897D500566E05 /* ChartViewPortHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartViewPortHandler.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5BA8EC3C1A9D14DC00CE82E1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B680D1D1A9D16F90026A057 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5B680D1E1A9D170B0026A057 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5B680D1C1A9D16F90026A057 /* UIKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5B6A546C1AA5D2D0000F57C2 /* Animation */ = { + isa = PBXGroup; + children = ( + 5B6A546D1AA5D2DC000F57C2 /* ChartAnimator.swift */, + ); + path = Animation; + sourceTree = ""; + }; + 5B759ED41A9F98A90039D97F /* Renderers */ = { + isa = PBXGroup; + children = ( + 5B6A54711AA5DCA8000F57C2 /* ChartAxisRendererBase.swift */, + 5B6A54961AA66AD2000F57C2 /* BarChartRenderer.swift */, + 5B6A54941AA66AC0000F57C2 /* CandleStickChartRenderer.swift */, + 5B6A54921AA66AAB000F57C2 /* CombinedChartRenderer.swift */, + 5B6A54901AA66A8D000F57C2 /* ChartDataRendererBase.swift */, + 5B6A548E1AA66A7A000F57C2 /* HorizontalBarChartRenderer.swift */, + 5B6A548C1AA66A60000F57C2 /* ChartLegendRenderer.swift */, + 5B6A548A1AA66A3D000F57C2 /* LineChartRenderer.swift */, + 5B6A54881AA66A1A000F57C2 /* PieChartRenderer.swift */, + 5B6A54861AA669F4000F57C2 /* RadarChartRenderer.swift */, + 5B6A546F1AA5DB34000F57C2 /* ChartRendererBase.swift */, + 5B6A54841AA669C9000F57C2 /* ScatterChartRenderer.swift */, + 5B6A54731AA5DEDC000F57C2 /* ChartXAxisRenderer.swift */, + 5B6A54751AA5DEE3000F57C2 /* ChartXAxisRendererBarChart.swift */, + 5B6A547B1AA5DF02000F57C2 /* ChartXAxisRendererHorizontalBarChart.swift */, + 5B6A54771AA5DEF0000F57C2 /* ChartXAxisRendererRadarChart.swift */, + 5B6A547D1AA5DF1A000F57C2 /* ChartYAxisRenderer.swift */, + 5B6A547F1AA5DF28000F57C2 /* ChartYAxisRendererHorizontalBarChart.swift */, + 5B6A54811AA5DF34000F57C2 /* ChartYAxisRendererRadarChart.swift */, + ); + path = Renderers; + sourceTree = ""; + }; + 5BA8EC361A9D14DC00CE82E1 = { + isa = PBXGroup; + children = ( + 5BA8EC651A9D151C00CE82E1 /* Classes */, + 5BA8EC431A9D14DC00CE82E1 /* Supporting Files */, + 5BA8EC411A9D14DC00CE82E1 /* Products */, + 5B680D1E1A9D170B0026A057 /* Frameworks */, + ); + sourceTree = ""; + }; + 5BA8EC411A9D14DC00CE82E1 /* Products */ = { + isa = PBXGroup; + children = ( + 5BA8EC401A9D14DC00CE82E1 /* Charts.framework */, + ); + name = Products; + sourceTree = ""; + }; + 5BA8EC431A9D14DC00CE82E1 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 5B680D3C1A9D1AD90026A057 /* Charts.h */, + 5BA8EC441A9D14DC00CE82E1 /* Info.plist */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; + 5BA8EC651A9D151C00CE82E1 /* Classes */ = { + isa = PBXGroup; + children = ( + 5B6A546C1AA5D2D0000F57C2 /* Animation */, + 5BA8EC661A9D151C00CE82E1 /* Charts */, + 5BA8EC691A9D151C00CE82E1 /* Components */, + 5BA8EC6F1A9D151C00CE82E1 /* Data */, + 5BA8EC741A9D151C00CE82E1 /* Filters */, + 5B759ED41A9F98A90039D97F /* Renderers */, + 5BA8EC781A9D151C00CE82E1 /* Utils */, + ); + path = Classes; + sourceTree = ""; + }; + 5BA8EC661A9D151C00CE82E1 /* Charts */ = { + isa = PBXGroup; + children = ( + 5B6A54981AA66B14000F57C2 /* BarChartView.swift */, + 5B6A549A1AA66B2C000F57C2 /* BarLineChartViewBase.swift */, + 5B6A549C1AA66B3C000F57C2 /* CandleStickChartView.swift */, + 5BA8EC671A9D151C00CE82E1 /* ChartViewBase.swift */, + 5B6A549E1AA66B59000F57C2 /* CombinedChartView.swift */, + 5B6A54A01AA66B6A000F57C2 /* HorizontalBarChartView.swift */, + 5B6A54A21AA66B7C000F57C2 /* LineChartView.swift */, + 5B6A54A41AA66B92000F57C2 /* PieChartView.swift */, + 5B6A54A61AA66BA7000F57C2 /* PieRadarChartViewBase.swift */, + 5B6A54A81AA66BBA000F57C2 /* RadarChartView.swift */, + 5B6A54AA1AA66BC8000F57C2 /* ScatterChartView.swift */, + ); + path = Charts; + sourceTree = ""; + }; + 5BA8EC691A9D151C00CE82E1 /* Components */ = { + isa = PBXGroup; + children = ( + 5BA8EC6A1A9D151C00CE82E1 /* ChartAxisBase.swift */, + 5B6556F61AB72BA000FFBFD3 /* ChartComponentBase.swift */, + 5BA8EC6B1A9D151C00CE82E1 /* ChartLegend.swift */, + 5BA8EC6C1A9D151C00CE82E1 /* ChartLimitLine.swift */, + 5B6A546A1AA5C23F000F57C2 /* ChartMarker.swift */, + 5BA8EC6D1A9D151C00CE82E1 /* ChartXAxis.swift */, + 5BA8EC6E1A9D151C00CE82E1 /* ChartYAxis.swift */, + ); + path = Components; + sourceTree = ""; + }; + 5BA8EC6F1A9D151C00CE82E1 /* Data */ = { + isa = PBXGroup; + children = ( + 5B6A54B71AA74516000F57C2 /* BarChartData.swift */, + 5B6A54B81AA74516000F57C2 /* BarChartDataEntry.swift */, + 5B6A54B91AA74516000F57C2 /* BarChartDataSet.swift */, + 5B6A54BA1AA74516000F57C2 /* BarLineScatterCandleChartData.swift */, + 5B6A54BB1AA74516000F57C2 /* BarLineScatterCandleChartDataSet.swift */, + 5B6A54BC1AA74516000F57C2 /* CandleChartData.swift */, + 5B6A54BD1AA74516000F57C2 /* CandleChartDataEntry.swift */, + 5B6A54BE1AA74516000F57C2 /* CandleChartDataSet.swift */, + 5B6A54BF1AA74516000F57C2 /* CombinedChartData.swift */, + 5B6A54C01AA74516000F57C2 /* ChartData.swift */, + 5B6A54C11AA74516000F57C2 /* ChartDataEntry.swift */, + 5B6A54C21AA74516000F57C2 /* ChartDataSet.swift */, + 5B6A54C31AA74516000F57C2 /* LineChartData.swift */, + 5B6A54C41AA74516000F57C2 /* LineChartDataSet.swift */, + 5B6A54C51AA74516000F57C2 /* LineRadarChartDataSet.swift */, + 5B6A54C61AA74516000F57C2 /* PieChartData.swift */, + 5B6A54C71AA74516000F57C2 /* PieChartDataSet.swift */, + 5B6A54C81AA74516000F57C2 /* RadarChartData.swift */, + 5B6A54C91AA74516000F57C2 /* RadarChartDataSet.swift */, + 5B6A54CA1AA74516000F57C2 /* ScatterChartData.swift */, + 5B6A54CB1AA74516000F57C2 /* ScatterChartDataSet.swift */, + ); + path = Data; + sourceTree = ""; + }; + 5BA8EC741A9D151C00CE82E1 /* Filters */ = { + isa = PBXGroup; + children = ( + 5BA8EC751A9D151C00CE82E1 /* ChartDataApproximatorFilter.swift */, + 5BA8EC761A9D151C00CE82E1 /* ChartDataBaseFilter.swift */, + ); + path = Filters; + sourceTree = ""; + }; + 5BA8EC781A9D151C00CE82E1 /* Utils */ = { + isa = PBXGroup; + children = ( + 5BA8EC791A9D151C00CE82E1 /* ChartColorTemplates.swift */, + 5B4BCD3D1AA9C0A60063F019 /* ChartFillFormatter.swift */, + 5BA8EC7A1A9D151C00CE82E1 /* ChartHighlight.swift */, + 5BA8EC7B1A9D151C00CE82E1 /* ChartSelInfo.swift */, + 5B4BCD3F1AA9C4930063F019 /* ChartTransformer.swift */, + 5BA8EC7C1A9D151C00CE82E1 /* ChartUtils.swift */, + 5BD8F06C1AB897D500566E05 /* ChartViewPortHandler.swift */, + ); + path = Utils; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 5BA8EC3D1A9D14DC00CE82E1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B680D3D1A9D1AD90026A057 /* Charts.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 5BA8EC3F1A9D14DC00CE82E1 /* Charts */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5BA8EC561A9D14DC00CE82E1 /* Build configuration list for PBXNativeTarget "Charts" */; + buildPhases = ( + 5BA8EC3B1A9D14DC00CE82E1 /* Sources */, + 5BA8EC3C1A9D14DC00CE82E1 /* Frameworks */, + 5BA8EC3D1A9D14DC00CE82E1 /* Headers */, + 5BA8EC3E1A9D14DC00CE82E1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Charts; + productName = Charts; + productReference = 5BA8EC401A9D14DC00CE82E1 /* Charts.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5BA8EC371A9D14DC00CE82E1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = dcg; + TargetAttributes = { + 5BA8EC3F1A9D14DC00CE82E1 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 5BA8EC3A1A9D14DC00CE82E1 /* Build configuration list for PBXProject "Charts" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 5BA8EC361A9D14DC00CE82E1; + productRefGroup = 5BA8EC411A9D14DC00CE82E1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5BA8EC3F1A9D14DC00CE82E1 /* Charts */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5BA8EC3E1A9D14DC00CE82E1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5BA8EC3B1A9D14DC00CE82E1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B680D1F1A9D17C30026A057 /* ChartAxisBase.swift in Sources */, + 5B680D281A9D17C30026A057 /* ChartHighlight.swift in Sources */, + 5B4BCD3E1AA9C0A60063F019 /* ChartFillFormatter.swift in Sources */, + 5B6A54DE1AA74516000F57C2 /* RadarChartDataSet.swift in Sources */, + 5B680D211A9D17C30026A057 /* ChartLimitLine.swift in Sources */, + 5B6A54DB1AA74516000F57C2 /* PieChartData.swift in Sources */, + 5B6A54D91AA74516000F57C2 /* LineChartDataSet.swift in Sources */, + 5B6A54AC1AA66C1E000F57C2 /* ChartAxisRendererBase.swift in Sources */, + 5B6A54CF1AA74516000F57C2 /* BarLineScatterCandleChartData.swift in Sources */, + 5B6A54D11AA74516000F57C2 /* CandleChartData.swift in Sources */, + 5B6A54AB1AA66BC8000F57C2 /* ScatterChartView.swift in Sources */, + 5B6A54E01AA74516000F57C2 /* ScatterChartDataSet.swift in Sources */, + 5B6A548B1AA66A3D000F57C2 /* LineChartRenderer.swift in Sources */, + 5B6A54D01AA74516000F57C2 /* BarLineScatterCandleChartDataSet.swift in Sources */, + 5B6A54821AA5DF34000F57C2 /* ChartYAxisRendererRadarChart.swift in Sources */, + 5B6A54931AA66AAB000F57C2 /* CombinedChartRenderer.swift in Sources */, + 5B680D221A9D17C30026A057 /* ChartXAxis.swift in Sources */, + 5BA8EC891A9D151C00CE82E1 /* ChartDataBaseFilter.swift in Sources */, + 5B6A54A31AA66B7C000F57C2 /* LineChartView.swift in Sources */, + 5B6A54891AA66A1A000F57C2 /* PieChartRenderer.swift in Sources */, + 5B6A54DD1AA74516000F57C2 /* RadarChartData.swift in Sources */, + 5B6A54991AA66B14000F57C2 /* BarChartView.swift in Sources */, + 5B680D231A9D17C30026A057 /* ChartYAxis.swift in Sources */, + 5B6A54A91AA66BBA000F57C2 /* RadarChartView.swift in Sources */, + 5B6A548F1AA66A7A000F57C2 /* HorizontalBarChartRenderer.swift in Sources */, + 5B6A54741AA5DEDC000F57C2 /* ChartXAxisRenderer.swift in Sources */, + 5B6A547C1AA5DF02000F57C2 /* ChartXAxisRendererHorizontalBarChart.swift in Sources */, + 5B4BCD401AA9C4930063F019 /* ChartTransformer.swift in Sources */, + 5B6A54801AA5DF28000F57C2 /* ChartYAxisRendererHorizontalBarChart.swift in Sources */, + 5B6A54D21AA74516000F57C2 /* CandleChartDataEntry.swift in Sources */, + 5B6A54CC1AA74516000F57C2 /* BarChartData.swift in Sources */, + 5B6A54CE1AA74516000F57C2 /* BarChartDataSet.swift in Sources */, + 5B6A54871AA669F4000F57C2 /* RadarChartRenderer.swift in Sources */, + 5B6A548D1AA66A60000F57C2 /* ChartLegendRenderer.swift in Sources */, + 5B680D271A9D17C30026A057 /* ChartColorTemplates.swift in Sources */, + 5B6A54951AA66AC0000F57C2 /* CandleStickChartRenderer.swift in Sources */, + 5B680D291A9D17C30026A057 /* ChartSelInfo.swift in Sources */, + 5BA8EC7D1A9D151C00CE82E1 /* ChartViewBase.swift in Sources */, + 5B6A54DC1AA74516000F57C2 /* PieChartDataSet.swift in Sources */, + 5B6A54DA1AA74516000F57C2 /* LineRadarChartDataSet.swift in Sources */, + 5B6A54701AA5DB34000F57C2 /* ChartRendererBase.swift in Sources */, + 5B6A54761AA5DEE3000F57C2 /* ChartXAxisRendererBarChart.swift in Sources */, + 5B6A54851AA669C9000F57C2 /* ScatterChartRenderer.swift in Sources */, + 5B6A549D1AA66B3C000F57C2 /* CandleStickChartView.swift in Sources */, + 5BA8EC881A9D151C00CE82E1 /* ChartDataApproximatorFilter.swift in Sources */, + 5B6A549B1AA66B2C000F57C2 /* BarLineChartViewBase.swift in Sources */, + 5B6A54A51AA66B92000F57C2 /* PieChartView.swift in Sources */, + 5B6A54D81AA74516000F57C2 /* LineChartData.swift in Sources */, + 5B6A54911AA66A8D000F57C2 /* ChartDataRendererBase.swift in Sources */, + 5BD8F06D1AB897D500566E05 /* ChartViewPortHandler.swift in Sources */, + 5B6A54D51AA74516000F57C2 /* ChartData.swift in Sources */, + 5B6A54971AA66AD2000F57C2 /* BarChartRenderer.swift in Sources */, + 5B6A546B1AA5C23F000F57C2 /* ChartMarker.swift in Sources */, + 5B6A54D61AA74516000F57C2 /* ChartDataEntry.swift in Sources */, + 5B6A54DF1AA74516000F57C2 /* ScatterChartData.swift in Sources */, + 5B6A54D31AA74516000F57C2 /* CandleChartDataSet.swift in Sources */, + 5B6A54D71AA74516000F57C2 /* ChartDataSet.swift in Sources */, + 5B6A54781AA5DEF0000F57C2 /* ChartXAxisRendererRadarChart.swift in Sources */, + 5B6A54A71AA66BA7000F57C2 /* PieRadarChartViewBase.swift in Sources */, + 5B6A546E1AA5D2DC000F57C2 /* ChartAnimator.swift in Sources */, + 5B6A547E1AA5DF1A000F57C2 /* ChartYAxisRenderer.swift in Sources */, + 5B6A549F1AA66B59000F57C2 /* CombinedChartView.swift in Sources */, + 5B6556F71AB72BA000FFBFD3 /* ChartComponentBase.swift in Sources */, + 5B6A54CD1AA74516000F57C2 /* BarChartDataEntry.swift in Sources */, + 5B6A54D41AA74516000F57C2 /* CombinedChartData.swift in Sources */, + 5B680D2A1A9D17C30026A057 /* ChartUtils.swift in Sources */, + 5B680D201A9D17C30026A057 /* ChartLegend.swift in Sources */, + 5BD8F06E1AB89AD800566E05 /* HorizontalBarChartView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 5BA8EC541A9D14DC00CE82E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 5BA8EC551A9D14DC00CE82E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 5BA8EC571A9D14DC00CE82E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 5BA8EC581A9D14DC00CE82E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5BA8EC3A1A9D14DC00CE82E1 /* Build configuration list for PBXProject "Charts" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BA8EC541A9D14DC00CE82E1 /* Debug */, + 5BA8EC551A9D14DC00CE82E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5BA8EC561A9D14DC00CE82E1 /* Build configuration list for PBXNativeTarget "Charts" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5BA8EC571A9D14DC00CE82E1 /* Debug */, + 5BA8EC581A9D14DC00CE82E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5BA8EC371A9D14DC00CE82E1 /* Project object */; +} diff --git a/Charts/Classes/Animation/ChartAnimator.swift b/Charts/Classes/Animation/ChartAnimator.swift new file mode 100644 index 0000000000..6f3fad773d --- /dev/null +++ b/Charts/Classes/Animation/ChartAnimator.swift @@ -0,0 +1,149 @@ +// +// ChartAnimator.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +@objc +public protocol ChartAnimatorDelegate +{ + // Called when the Animator has stepped. + func chartAnimatorUpdated(chartAnimator: ChartAnimator); +} + +public class ChartAnimator: NSObject +{ + public weak var delegate: ChartAnimatorDelegate?; + + /// the phase that is animated and influences the drawn values on the y-axis + public var phaseX: CGFloat = 1.0 + + /// the phase that is animated and influences the drawn values on the y-axis + public var phaseY: CGFloat = 1.0 + + private var _startTime: NSTimeInterval = 0.0 + private var _displayLink: CADisplayLink! + private var _endTimeX: NSTimeInterval = 0.0 + private var _endTimeY: NSTimeInterval = 0.0 + private var _endTime: NSTimeInterval = 0.0 + private var _enabledX: Bool = false + private var _enabledY: Bool = false + + public override init() + { + super.init(); + } + + deinit + { + stop(); + } + + public func stop() + { + if (_displayLink != nil) + { + _displayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + _displayLink = nil; + + _enabledX = false; + _enabledY = false; + } + } + + @objc private func animationLoop() + { + var currentTime = CACurrentMediaTime(); + if (_enabledX) + { + var duration = _endTimeX - _startTime; + phaseX = duration == 0.0 ? 0.0 : CGFloat((currentTime - _startTime) / duration); + if (phaseX > 1.0) + { + phaseX = 1.0; + } + } + if (_enabledY) + { + var duration = _endTimeY - _startTime; + phaseY = duration == 0.0 ? 0.0 : CGFloat((currentTime - _startTime) / duration); + if (phaseY > 1.0) + { + phaseY = 1.0; + } + } + if (currentTime >= _endTime) + { + stop(); + } + if (delegate != nil) + { + delegate!.chartAnimatorUpdated(self); + } + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with + /// the specified animation time. + /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. + public func animateXY(#durationX: NSTimeInterval, durationY: NSTimeInterval) + { + stop(); + + _displayLink = CADisplayLink(target: self, selector: Selector("animationLoop")); + + _startTime = CACurrentMediaTime(); + _endTimeX = _startTime + durationX; + _endTimeY = _startTime + durationY; + _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY; + _enabledX = true; + _enabledY = true; + + _displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + } + + /// Animates the drawing / rendering of the chart the x-axis with + /// the specified animation time. + /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. + public func animateX(#duration: NSTimeInterval) + { + stop(); + + _displayLink = CADisplayLink(target: self, selector: Selector("animationLoop")); + + _startTime = CACurrentMediaTime(); + _endTimeX = _startTime + duration; + _endTime = _endTimeX; + _enabledX = true; + _enabledY = false; + + _displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + } + + /// Animates the drawing / rendering of the chart the y-axis with + /// the specified animation time. + /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. + public func animateY(#duration: NSTimeInterval) + { + stop(); + + _displayLink = CADisplayLink(target: self, selector: Selector("animationLoop")); + + _startTime = CACurrentMediaTime(); + _endTimeY = _startTime + duration; + _endTime = _endTimeY; + _enabledX = false; + _enabledY = true; + + _displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/BarChartView.swift b/Charts/Classes/Charts/BarChartView.swift new file mode 100644 index 0000000000..6befd6b844 --- /dev/null +++ b/Charts/Classes/Charts/BarChartView.swift @@ -0,0 +1,321 @@ +// +// BarChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Chart that draws bars. +public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate +{ + /// flag that enables or disables the highlighting arrow + private var _drawHighlightArrowEnabled = false + + /// if set to true, all values are drawn above their bars, instead of below their top + private var _drawValueAboveBarEnabled = true + + /// if set to true, all values of a stack are drawn individually, and not just their sum + private var _drawValuesForWholeStackEnabled = true + + /// if set to true, a grey area is darawn behind each bar that indicates the maximum value + private var _drawBarShadowEnabled = false + + internal override func initialize() + { + super.initialize(); + + renderer = BarChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler); + _xAxisRenderer = ChartXAxisRendererBarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer, chart: self); + + _chartXMin = -0.5; + } + + internal override func calcMinMax() + { + super.calcMinMax(); + + if (_data === nil) + { + return; + } + + var barData = _data as! BarChartData; + + // increase deltax by 1 because the bars have a width of 1 + _deltaX += 0.5; + + // extend xDelta to make space for multiple datasets (if ther are one) + _deltaX *= CGFloat(_data.dataSetCount); + + var maxEntry = 0; + + for (var i = 0, count = barData.dataSetCount; i < count; i++) + { + var set = barData.getDataSetByIndex(i); + + if (maxEntry < set!.entryCount) + { + maxEntry = set!.entryCount; + } + } + + var groupSpace = barData.groupSpace; + _deltaX += CGFloat(maxEntry) * groupSpace; + _chartXMax = Float(_deltaX) - _chartXMin; + } + + /// Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the BarChart. + public override func getHighlightByTouchPoint(var pt: CGPoint) -> ChartHighlight! + { + if (_dataNotSet || _data === nil) + { + println("Can't select by touch. No data set."); + return nil; + } + + _leftAxisTransformer.pixelToValue(&pt); + + if (pt.x < CGFloat(_chartXMin) || pt.x > CGFloat(_chartXMax)) + { + return nil; + } + + return getHighlight(xPosition: pt.x, yPosition: pt.y); + } + + /// Returns the correct Highlight object (including xIndex and dataSet-index) for the specified touch position. + internal func getHighlight(#xPosition: CGFloat, yPosition: CGFloat) -> ChartHighlight! + { + if (_dataNotSet || _data === nil) + { + return nil; + } + + var barData = _data as! BarChartData!; + + var setCount = barData.dataSetCount; + var valCount = barData.xValCount; + var dataSetIndex = 0; + var xIndex = 0; + + if (!barData.isGrouped) + { // only one dataset exists + + xIndex = Int(round(xPosition)); + + // check bounds + if (xIndex < 0) + { + xIndex = 0; + } + else if (xIndex >= valCount) + { + xIndex = valCount - 1; + } + } + else + { // if this bardata is grouped into more datasets + + // calculate how often the group-space appears + var steps = Int(xPosition / (CGFloat(setCount) + CGFloat(barData.groupSpace))); + + var groupSpaceSum = barData.groupSpace * CGFloat(steps); + + var baseNoSpace = xPosition - groupSpaceSum; + + dataSetIndex = Int(baseNoSpace) % setCount; + xIndex = Int(baseNoSpace) / setCount; + + // check bounds + if (xIndex < 0) + { + xIndex = 0; + dataSetIndex = 0; + } + else if (xIndex >= valCount) + { + xIndex = valCount - 1; + dataSetIndex = setCount - 1; + } + + // check bounds + if (dataSetIndex < 0) + { + dataSetIndex = 0; + } + else if (dataSetIndex >= setCount) + { + dataSetIndex = setCount - 1; + } + } + + var dataSet = barData.getDataSetByIndex(dataSetIndex) as! BarChartDataSet!; + if (!dataSet.isStacked) + { + return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex); + } + else + { + return getStackedHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex, yValue: Float(yPosition)); + } + } + + /// This method creates the Highlight object that also indicates which value of a stacked BarEntry has been selected. + internal func getStackedHighlight(#xIndex: Int, dataSetIndex: Int, yValue: Float) -> ChartHighlight! + { + var dataSet = _data.getDataSetByIndex(dataSetIndex); + var entry = dataSet.entryForXIndex(xIndex) as! BarChartDataEntry!; + + if (entry !== nil) + { + var stackIndex = entry.getClosestIndexAbove(yValue); + return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex, stackIndex: stackIndex); + } + else + { + return nil; + } + } + + /// Returns the bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be found in the charts data. + public func getBarBounds(e: BarChartDataEntry) -> CGRect! + { + var set = _data.getDataSetForEntry(e) as! BarChartDataSet!; + + if (set === nil) + { + return nil; + } + + var barspace = set.barSpace; + var y = CGFloat(e.value); + var x = CGFloat(e.xIndex); + + var barWidth: CGFloat = 0.5; + + var spaceHalf = barspace / 2.0; + var left = x - barWidth + spaceHalf; + var right = x + barWidth - spaceHalf; + var top = y >= 0.0 ? y : 0.0; + var bottom = y <= 0.0 ? y : 0.0; + + var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top); + + getTransformer(set.axisDependency).rectValueToPixel(&bounds); + + return bounds; + } + + // MARK: Accessors + + /// flag that enables or disables the highlighting arrow + public var drawHighlightArrowEnabled: Bool + { + get { return _drawHighlightArrowEnabled; } + set + { + _drawHighlightArrowEnabled = newValue; + setNeedsDisplay(); + } + } + + /// if set to true, all values are drawn above their bars, instead of below their top + public var drawValueAboveBarEnabled: Bool + { + get { return _drawValueAboveBarEnabled; } + set + { + _drawValueAboveBarEnabled = newValue; + setNeedsDisplay(); + } + } + + /// if set to true, all values of a stack are drawn individually, and not just their sum + public var drawValuesForWholeStackEnabled: Bool + { + get { return _drawValuesForWholeStackEnabled; } + set + { + _drawValuesForWholeStackEnabled = newValue; + setNeedsDisplay(); + } + } + + /// if set to true, a grey area is drawn behind each bar that indicates the maximum value + public var drawBarShadowEnabled: Bool + { + get { return _drawBarShadowEnabled; } + set + { + _drawBarShadowEnabled = newValue; + setNeedsDisplay(); + } + } + + /// returns true if drawing the highlighting arrow is enabled, false if not + public var isDrawHighlightArrowEnabled: Bool { return drawHighlightArrowEnabled; } + + /// returns true if drawing values above bars is enabled, false if not + public var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled; } + + /// returns true if all values of a stack are drawn, and not just their sum + public var isDrawValuesForWholeStackEnabled: Bool { return drawValuesForWholeStackEnabled; } + + /// returns true if drawing shadows (maxvalue) for each bar is enabled, false if not + public var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled; } + + // MARK: - BarChartRendererDelegate + + public func barChartRendererData(renderer: BarChartRenderer) -> BarChartData! + { + return _data as! BarChartData!; + } + + public func barChartRenderer(renderer: BarChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return getTransformer(which); + } + + public func barChartRendererMaxVisibleValueCount(renderer: BarChartRenderer) -> Int + { + return maxVisibleValueCount; + } + + public func barChartDefaultRendererValueFormatter(renderer: BarChartRenderer) -> NSNumberFormatter! + { + return valueFormatter; + } + + public func barChartRendererChartXMax(renderer: BarChartRenderer) -> Float + { + return chartXMax; + } + + public func barChartIsDrawHighlightArrowEnabled(renderer: BarChartRenderer) -> Bool + { + return drawHighlightArrowEnabled; + } + + public func barChartIsDrawValueAboveBarEnabled(renderer: BarChartRenderer) -> Bool + { + return drawValueAboveBarEnabled; + } + + public func barChartIsDrawValuesForWholeStackEnabled(renderer: BarChartRenderer) -> Bool + { + return drawValuesForWholeStackEnabled; + } + + public func barChartIsDrawBarShadowEnabled(renderer: BarChartRenderer) -> Bool + { + return drawBarShadowEnabled; + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/BarLineChartViewBase.swift b/Charts/Classes/Charts/BarLineChartViewBase.swift new file mode 100644 index 0000000000..8acb911e99 --- /dev/null +++ b/Charts/Classes/Charts/BarLineChartViewBase.swift @@ -0,0 +1,1338 @@ +// +// BarLineChartViewBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Base-class of LineChart, BarChart, ScatterChart and CandleStickChart. +public class BarLineChartViewBase: ChartViewBase +{ + /// the maximum number of entried to which values will be drawn + internal var _maxVisibleValueCount = 100 + + private var _pinchZoomEnabled = false + private var _doubleTapToZoomEnabled = true + private var _dragEnabled = true + + private var _scaleXEnabled = true + private var _scaleYEnabled = true + + /// the color for the background of the chart-drawing area (everything behind the grid lines). + public var gridBackgroundColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) + + public var borderColor = UIColor.blackColor() + public var borderLineWidth: CGFloat = 1.0 + + /// if set to true, the highlight indicator (lines for linechart, dark bar for barchart) will be drawn upon selecting values. + public var highlightIndicatorEnabled = true + + /// flag indicating if the grid background should be drawn or not + public var drawGridBackgroundEnabled = true + + /// Sets drawing the borders rectangle to true. If this is enabled, there is no point drawing the axis-lines of x- and y-axis. + public var drawBordersEnabled = false + + /// the object representing the labels on the y-axis, this object is prepared + /// in the pepareYLabels() method + internal var _leftAxis: ChartYAxis! + internal var _rightAxis: ChartYAxis! + + /// the object representing the labels on the x-axis + internal var _xAxis: ChartXAxis! + + internal var _leftYAxisRenderer: ChartYAxisRenderer! + internal var _rightYAxisRenderer: ChartYAxisRenderer! + + internal var _leftAxisTransformer: ChartTransformer! + internal var _rightAxisTransformer: ChartTransformer! + + internal var _xAxisRenderer: ChartXAxisRenderer! + + private var _tapGestureRecognizer: UITapGestureRecognizer! + private var _doubleTapGestureRecognizer: UITapGestureRecognizer! + private var _pinchGestureRecognizer: UIPinchGestureRecognizer! + private var _panGestureRecognizer: UIPanGestureRecognizer! + + /// flag that indicates if a custom viewport offset has been set + private var _customViewPortEnabled = false + + public override init(frame: CGRect) + { + super.init(frame: frame); + } + + public required init(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder); + } + + internal override func initialize() + { + super.initialize(); + + _leftAxis = ChartYAxis(position: .Left); + _rightAxis = ChartYAxis(position: .Right); + + _xAxis = ChartXAxis(); + + _leftAxisTransformer = ChartTransformer(viewPortHandler: _viewPortHandler); + _rightAxisTransformer = ChartTransformer(viewPortHandler: _viewPortHandler); + + _leftYAxisRenderer = ChartYAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: _leftAxis, transformer: _leftAxisTransformer); + _rightYAxisRenderer = ChartYAxisRenderer(viewPortHandler: _viewPortHandler, yAxis: _rightAxis, transformer: _rightAxisTransformer); + + _xAxisRenderer = ChartXAxisRenderer(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer); + + _tapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapGestureRecognized:")); + _doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("doubleTapGestureRecognized:")); + _doubleTapGestureRecognizer.numberOfTapsRequired = 2; + _pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: Selector("pinchGestureRecognized:")); + _panGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("panGestureRecognized:")); + + self.addGestureRecognizer(_tapGestureRecognizer); + if (_doubleTapToZoomEnabled) + { + self.addGestureRecognizer(_doubleTapGestureRecognizer); + } + updateScaleGestureRecognizers(); + if (_dragEnabled) + { + self.addGestureRecognizer(_panGestureRecognizer); + } + } + + public override func drawRect(rect: CGRect) + { + super.drawRect(rect); + + if (_dataNotSet) + { + return; + } + + let context = UIGraphicsGetCurrentContext(); + + if (xAxis.isAdjustXLabelsEnabled) + { + calcModulus(); + } + + // execute all drawing commands + drawGridBackground(context: context); + + if (_leftAxis.isEnabled) + { + _leftYAxisRenderer?.computeAxis(yMin: _leftAxis.axisMinimum, yMax: _leftAxis.axisMaximum); + } + if (_rightAxis.isEnabled) + { + _rightYAxisRenderer?.computeAxis(yMin: _rightAxis.axisMinimum, yMax: _rightAxis.axisMaximum); + } + + _xAxisRenderer?.calcXBounds(_xAxisRenderer.transformer); + _leftYAxisRenderer?.calcXBounds(_xAxisRenderer.transformer); + _rightYAxisRenderer?.calcXBounds(_xAxisRenderer.transformer); + + _xAxisRenderer?.renderAxisLine(context: context); + _leftYAxisRenderer?.renderAxisLine(context: context); + _rightYAxisRenderer?.renderAxisLine(context: context); + + // make sure the graph values and grid cannot be drawn outside the content-rect + CGContextSaveGState(context); + + CGContextClipToRect(context, _viewPortHandler.contentRect); + + _xAxisRenderer?.renderGridLines(context: context); + _leftYAxisRenderer?.renderGridLines(context: context); + _rightYAxisRenderer?.renderGridLines(context: context); + + renderer?.drawData(context: context); + + _leftYAxisRenderer?.renderLimitLines(context: context); + _rightYAxisRenderer?.renderLimitLines(context: context); + + // if highlighting is enabled + if (highlightEnabled && highlightIndicatorEnabled && valuesToHighlight()) + { + renderer?.drawHighlighted(context: context, indices: _indicesToHightlight); + } + + // Removes clipping rectangle + CGContextRestoreGState(context); + + renderer!.drawExtras(context: context); + + _xAxisRenderer.renderAxisLabels(context: context); + _leftYAxisRenderer.renderAxisLabels(context: context); + _rightYAxisRenderer.renderAxisLabels(context: context); + + renderer!.drawValues(context: context); + + _legendRenderer.renderLegend(context: context, legend: _legend); + // drawLegend(); + + drawMarkers(context: context); + + drawDescription(context: context); + } + + internal func prepareValuePxMatrix() + { + _rightAxisTransformer.prepareMatrixValuePx(chartXMin: _chartXMin, deltaX: _deltaX, deltaY: CGFloat(_rightAxis.axisRange), chartYMin: _rightAxis.axisMinimum); + _leftAxisTransformer.prepareMatrixValuePx(chartXMin: _chartXMin, deltaX: _deltaX, deltaY: CGFloat(_leftAxis.axisRange), chartYMin: _leftAxis.axisMinimum); + } + + internal func prepareOffsetMatrix() + { + _rightAxisTransformer.prepareMatrixOffset(_rightAxis.isInverted); + _leftAxisTransformer.prepareMatrixOffset(_leftAxis.isInverted); + } + + public override func notifyDataSetChanged() + { + if (_dataNotSet) + { + return; + } + + calcMinMax(); + + _leftAxis?._defaultValueFormatter = _defaultValueFormatter; + _rightAxis?._defaultValueFormatter = _defaultValueFormatter; + + _leftYAxisRenderer?.computeAxis(yMin: _leftAxis.axisMinimum, yMax: _leftAxis.axisMaximum); + _rightYAxisRenderer?.computeAxis(yMin: _rightAxis.axisMinimum, yMax: _rightAxis.axisMaximum); + + _xAxisRenderer?.computeAxis(xValAverageLength: _data.xValAverageLength, xValues: _data.xVals); + + _legend = _legendRenderer?.computeLegend(_data, legend: _legend); + + calculateOffsets(); + + setNeedsDisplay(); + } + + internal override func calcMinMax() + { + var minLeft = _data.getYMin(.Left); + var maxLeft = _data.getYMax(.Left); + var minRight = _data.getYMin(.Right); + var maxRight = _data.getYMax(.Right); + + var leftRange = abs(maxLeft - (_leftAxis.isStartAtZeroEnabled ? 0.0 : minLeft)); + var rightRange = abs(maxRight - (_rightAxis.isStartAtZeroEnabled ? 0.0 : minRight)); + + var topSpaceLeft = leftRange * Float(_leftAxis.spaceTop); + var topSpaceRight = rightRange * Float(_rightAxis.spaceTop); + var bottomSpaceLeft = leftRange * Float(_leftAxis.spaceBottom); + var bottomSpaceRight = rightRange * Float(_rightAxis.spaceBottom); + + _chartXMax = Float(_data.xVals.count - 1); + _deltaX = CGFloat(abs(_chartXMax - _chartXMin)); + + _leftAxis.axisMaximum = !isnan(_leftAxis.customAxisMax) ? _leftAxis.customAxisMax : (maxLeft + topSpaceLeft); + _rightAxis.axisMaximum = !isnan(_rightAxis.customAxisMax) ? _rightAxis.customAxisMax : (maxRight + topSpaceRight); + _leftAxis.axisMinimum = !isnan(_leftAxis.customAxisMin) ? _leftAxis.customAxisMin : (minLeft - bottomSpaceLeft); + _rightAxis.axisMinimum = !isnan(_rightAxis.customAxisMin) ? _rightAxis.customAxisMin : (minRight - bottomSpaceRight); + + // consider starting at zero (0) + if (_leftAxis.isStartAtZeroEnabled) + { + _leftAxis.axisMinimum = 0.0; + } + + if (_rightAxis.isStartAtZeroEnabled) + { + _rightAxis.axisMinimum = 0.0; + } + + _leftAxis.axisRange = abs(_leftAxis.axisMaximum - _leftAxis.axisMinimum); + _rightAxis.axisRange = abs(_rightAxis.axisMaximum - _rightAxis.axisMinimum); + } + + internal override func calculateOffsets() + { + if (!_customViewPortEnabled) + { + var offsetLeft = CGFloat(0.0); + var offsetRight = CGFloat(0.0); + var offsetTop = CGFloat(0.0); + var offsetBottom = CGFloat(0.0); + + // setup offsets for legend + if (_legend !== nil && _legend.isEnabled) + { + if (_legend.position == .RightOfChart + || _legend.position == .RightOfChartCenter) + { + offsetRight += _legend.textWidthMax + _legend.xOffset * 2.0; + } + else if (_legend.position == .BelowChartLeft + || _legend.position == .BelowChartRight + || _legend.position == .BelowChartCenter) + { + + offsetBottom += _legend.textHeightMax * 3.0; + } + } + + // offsets for y-labels + if (leftAxis.needsOffset) + { + offsetLeft += leftAxis.requiredSize().width; + } + + if (rightAxis.needsOffset) + { + offsetRight += rightAxis.requiredSize().width; + } + + var xlabelheight = xAxis.labelHeight * 2.0; + + if (xAxis.isEnabled) + { + // offsets for x-labels + if (xAxis.labelPosition == .Bottom) + { + offsetBottom += xlabelheight; + } + else if (xAxis.labelPosition == .Top) + { + offsetTop += xlabelheight; + } + else if (xAxis.labelPosition == .BothSided) + { + offsetBottom += xlabelheight; + offsetTop += xlabelheight; + } + } + + var min = CGFloat(10.0); + + _viewPortHandler.restrainViewPort( + offsetLeft: max(min, offsetLeft), + offsetTop: max(min, offsetTop), + offsetRight: max(min, offsetRight), + offsetBottom: max(min, offsetBottom)); + } + + prepareOffsetMatrix(); + prepareValuePxMatrix(); + } + + + /// calculates the modulus for x-labels and grid + internal func calcModulus() + { + if (_xAxis === nil) + { + return; + } + + _xAxis.axisLabelModulus = Int(ceil((CGFloat(_data.xValCount) * _xAxis.labelWidth) / (_viewPortHandler.contentWidth * _viewPortHandler.touchMatrix.a))); + + if (_xAxis.axisLabelModulus < 1) + { + _xAxis.axisLabelModulus = 1; + } + } + + public override func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + { + var xPos = CGFloat(entry.xIndex); + + if (self.isKindOfClass(BarChartView)) + { + var bd = _data as! BarChartData; + var space = bd.groupSpace; + var j = _data.getDataSetByIndex(dataSetIndex)!.entryIndex(entry: entry, isEqual: true); + + var x = CGFloat(j * (_data.dataSetCount - 1) + dataSetIndex) + space * CGFloat(j) + space / 2.0; + + xPos += x; + } + + // position of the marker depends on selected value index and value + var pt = CGPoint(x: xPos, y: CGFloat(entry.value) * _animator.phaseY); + + getTransformer(_data.getDataSetByIndex(dataSetIndex)!.axisDependency).pointValueToPixel(&pt); + + return pt; + } + + /// draws the grid background + internal func drawGridBackground(#context: CGContext) + { + if (drawGridBackgroundEnabled || drawBordersEnabled) + { + CGContextSaveGState(context); + } + + if (drawGridBackgroundEnabled) + { + // draw the grid background + CGContextSetFillColorWithColor(context, gridBackgroundColor.CGColor); + CGContextFillRect(context, _viewPortHandler.contentRect); + } + + if (drawBordersEnabled) + { + CGContextSetLineWidth(context, borderLineWidth); + CGContextSetStrokeColorWithColor(context, borderColor.CGColor); + CGContextStrokeRect(context, _viewPortHandler.contentRect); + } + + if (drawGridBackgroundEnabled || drawBordersEnabled) + { + CGContextRestoreGState(context); + } + } + + /// Returns the Transformer class that contains all matrices and is + /// responsible for transforming values into pixels on the screen and + /// backwards. + public func getTransformer(which: ChartYAxis.AxisDependency) -> ChartTransformer + { + if (which == .Left) + { + return _leftAxisTransformer; + } + else + { + return _rightAxisTransformer; + } + } + + // MARK: - Scaling and Gestures + + private var _gestureStartMatrix = CGAffineTransformIdentity; + private var _gestureScaleMatrix = CGAffineTransformIdentity; + private var _gesturePanMatrix = CGAffineTransformIdentity; + + private enum GestureScaleAxis + { + case Both + case X + case Y + } + + private var _isDragging = false; + private var _isScaling = false; + private var _gestureScaleAxis = GestureScaleAxis.Both; + private var _closestDataSetToTouch: ChartDataSet!; + + /// the last highlighted object + private var _lastHighlighted: ChartHighlight!; + + @objc private func tapGestureRecognized(recognizer: UITapGestureRecognizer) + { + if (_dataNotSet) + { + return; + } + + if (recognizer.state == UIGestureRecognizerState.Ended) + { + var h = getHighlightByTouchPoint(recognizer.locationInView(self)); + + if (h === nil || h!.isEqual(_lastHighlighted)) + { + self.highlightValue(highlight: nil, callDelegate: true); + _lastHighlighted = nil; + } + else + { + _lastHighlighted = h; + self.highlightValue(highlight: h, callDelegate: true); + } + } + } + + @objc private func doubleTapGestureRecognized(recognizer: UITapGestureRecognizer) + { + if (_dataNotSet) + { + return; + } + + if (recognizer.state == UIGestureRecognizerState.Ended) + { + if (!_dataNotSet && _doubleTapToZoomEnabled) + { + var location = recognizer.locationInView(self); + location.x = location.x - _viewPortHandler.offsetLeft; + + if (isAnyAxisInverted && _closestDataSetToTouch !== nil && getAxis(_closestDataSetToTouch.axisDependency).isInverted) + { + location.y = -(location.y - _viewPortHandler.offsetTop); + } + else + { + location.y = -(self.bounds.size.height - location.y - _viewPortHandler.offsetBottom); + } + + self.zoom(1.4, scaleY: 1.4, x: location.x, y: location.y); + } + } + } + + @objc private func pinchGestureRecognized(recognizer: UIPinchGestureRecognizer) + { + if (recognizer.state == UIGestureRecognizerState.Began) + { + if (!_dataNotSet && (_pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled)) + { + if (!_isDragging) + { + _gestureStartMatrix = _viewPortHandler.touchMatrix; + } + _isScaling = true; + + if (_pinchZoomEnabled) + { + _gestureScaleAxis = .Both; + } + else + { + var x = abs(recognizer.locationInView(self).x - recognizer.locationOfTouch(1, inView: self).x); + var y = abs(recognizer.locationInView(self).y - recognizer.locationOfTouch(1, inView: self).y); + + if (x > y) + { + _gestureScaleAxis = .X; + } + else + { + _gestureScaleAxis = .Y; + } + } + } + } + else if (recognizer.state == UIGestureRecognizerState.Ended) + { + if (_isScaling) + { + _isScaling = false; + + var location = recognizer.locationInView(self); + location.x = location.x - _viewPortHandler.offsetLeft; + + if (isAnyAxisInverted && _closestDataSetToTouch !== nil && getAxis(_closestDataSetToTouch.axisDependency).isInverted) + { + location.y = -(location.y - _viewPortHandler.offsetTop); + } + else + { + location.y = -(self.bounds.size.height - location.y - _viewPortHandler.offsetBottom); + } + + _gestureScaleMatrix = CGAffineTransformMakeTranslation(location.x, location.y); + _gestureScaleMatrix = CGAffineTransformScale(_gestureScaleMatrix, + (_gestureScaleAxis == .Both || _gestureScaleAxis == .X) && _scaleXEnabled ? recognizer.scale : 1.0, + (_gestureScaleAxis == .Both || _gestureScaleAxis == .Y) && _scaleYEnabled ? recognizer.scale : 1.0); + _gestureScaleMatrix = CGAffineTransformTranslate(_gestureScaleMatrix, + -location.x, -location.y); + + var matrix = CGAffineTransformConcat(_gestureStartMatrix, _gestureScaleMatrix); + if (_isDragging) + { + matrix = CGAffineTransformConcat(matrix, _gesturePanMatrix); + } + + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + + // Save the matrix changes to the _gestureStartMatrix + + _gestureStartMatrix = CGAffineTransformConcat(_gestureStartMatrix, _gestureScaleMatrix); + } + } + else if (recognizer.state == UIGestureRecognizerState.Cancelled) + { + if (_isScaling) + { + _isScaling = false; + + _viewPortHandler.refresh(newMatrix: _gestureStartMatrix, chart: self, invalidate: true); + } + } + else if (recognizer.state == UIGestureRecognizerState.Changed) + { + if (_isScaling) + { + var location = recognizer.locationInView(self); + location.x = location.x - _viewPortHandler.offsetLeft; + + if (isAnyAxisInverted && _closestDataSetToTouch !== nil && getAxis(_closestDataSetToTouch.axisDependency).isInverted) + { + location.y = -(location.y - _viewPortHandler.offsetTop); + } + else + { + location.y = -(_viewPortHandler.chartHeight - location.y - _viewPortHandler.offsetBottom); + } + + _gestureScaleMatrix = CGAffineTransformMakeTranslation(location.x, location.y); + _gestureScaleMatrix = CGAffineTransformScale(_gestureScaleMatrix, + (_gestureScaleAxis == .Both || _gestureScaleAxis == .X) && _scaleXEnabled ? recognizer.scale : 1.0, + (_gestureScaleAxis == .Both || _gestureScaleAxis == .Y) && _scaleYEnabled ? recognizer.scale : 1.0); + _gestureScaleMatrix = CGAffineTransformTranslate(_gestureScaleMatrix, + -location.x, -location.y); + + var matrix = CGAffineTransformConcat(_gestureStartMatrix, _gestureScaleMatrix); + if (_isDragging) + { + matrix = CGAffineTransformConcat(matrix, _gesturePanMatrix); + } + + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + } + } + } + + @objc private func panGestureRecognized(recognizer: UIPanGestureRecognizer) + { + if (recognizer.state == UIGestureRecognizerState.Began) + { + if (!_dataNotSet && _dragEnabled && !self.hasNoDragOffset || !self.isFullyZoomedOut) + { + if (!_isScaling) + { + _gestureStartMatrix = _viewPortHandler.touchMatrix; + } + _isDragging = true; + + _closestDataSetToTouch = getDataSetByTouchPoint(recognizer.locationOfTouch(0, inView: self)); + } + } + else if (recognizer.state == UIGestureRecognizerState.Ended) + { + if (_isDragging) + { + _isDragging = false; + + var translation = recognizer.translationInView(self); + + if (isAnyAxisInverted && _closestDataSetToTouch !== nil + && getAxis(_closestDataSetToTouch.axisDependency).isInverted) + { + translation.y = -translation.y; + } + + _gesturePanMatrix = CGAffineTransformMakeTranslation(translation.x, translation.y); + + var matrix = _isScaling ? CGAffineTransformConcat(_gestureStartMatrix, _gestureScaleMatrix) : _gestureStartMatrix; + matrix = CGAffineTransformConcat(matrix, _gesturePanMatrix); + + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + + // Save the matrix changes to the _gestureStartMatrix + + if (_isScaling) + { + translation = CGPointApplyAffineTransform(translation, _gestureScaleMatrix); + _gesturePanMatrix = CGAffineTransformMakeTranslation(translation.x, translation.y); + } + + _gestureStartMatrix = CGAffineTransformConcat(_gestureStartMatrix, _gesturePanMatrix); + } + } + else if (recognizer.state == UIGestureRecognizerState.Cancelled) + { + if (_isDragging) + { + _isDragging = false; + + _viewPortHandler.refresh(newMatrix: _gestureStartMatrix, chart: self, invalidate: true); + } + } + else if (recognizer.state == UIGestureRecognizerState.Changed) + { + if (_isDragging) + { + var translation = recognizer.translationInView(self); + + if (isAnyAxisInverted && _closestDataSetToTouch !== nil + && getAxis(_closestDataSetToTouch.axisDependency).isInverted) + { + translation.y = -translation.y; + } + + _gesturePanMatrix = CGAffineTransformMakeTranslation(translation.x, translation.y); + + var matrix = _isScaling ? CGAffineTransformConcat(_gestureStartMatrix, _gestureScaleMatrix) : _gestureStartMatrix; + matrix = CGAffineTransformConcat(matrix, _gesturePanMatrix); + + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + } + } + } + + /// Zooms in by 1.4f, into the charts center. center. + public func zoomIn() + { + var matrix = _viewPortHandler.zoomIn(x: self.bounds.size.width / 2.0, y: -(self.bounds.size.height / 2.0)); + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + } + + /// Zooms out by 0.7f, from the charts center. center. + public func zoomOut() + { + var matrix = _viewPortHandler.zoomOut(x: self.bounds.size.width / 2.0, y: -(self.bounds.size.height / 2.0)); + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + } + + /// Zooms in or out by the given scale factor. x and y are the coordinates + /// (in pixels) of the zoom center. + /// + /// :param: scaleX if < 1f --> zoom out, if > 1f --> zoom in + /// :param: scaleY if < 1f --> zoom out, if > 1f --> zoom in + /// :param: x + /// :param: y + public func zoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) + { + var matrix = _viewPortHandler.zoom(scaleX: scaleX, scaleY: scaleY, x: x, y: -y); + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + } + + /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. + public func fitScreen() + { + var matrix = _viewPortHandler.fitScreen(); + _viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true); + } + + /// Sets the minimum scale value to which can be zoomed out. 1f = fitScreen + public func setScaleMinima(scaleX: CGFloat, scaleY: CGFloat) + { + _viewPortHandler.setMinimumScaleX(scaleX); + _viewPortHandler.setMinimumScaleY(scaleY); + } + + /// Sets the size of the area (range on the x-axis) that should be maximum + /// visible at once. If this is e.g. set to 10, no more than 10 values on the + /// x-axis can be viewed at once without scrolling. + public func setVisibleXRange(xRange: CGFloat) + { + var xScale = _deltaX / (xRange + 0.01); + _viewPortHandler.setMinimumScaleX(xScale); + } + + /// Sets the size of the area (range on the y-axis) that should be maximum visible at once. + /// + /// :param: yRange + /// :param: axis - the axis for which this limit should apply + public func setVisibleYRange(yRange: CGFloat, axis: ChartYAxis.AxisDependency) + { + var yScale = getDeltaY(axis) / yRange; + _viewPortHandler.setMinimumScaleY(yScale); + } + + /// Moves the left side of the current viewport to the specified x-index. + public func moveViewToX(xIndex: Int) + { + var pt = CGPoint(x: CGFloat(xIndex), y: 0.0); + + getTransformer(.Left).pointValueToPixel(&pt); + _viewPortHandler.centerViewPort(pt: pt, chart: self); + } + + /// Centers the viewport to the specified y-value on the y-axis. + /// + /// :param: yValue + /// :param: axis - which axis should be used as a reference for the y-axis + public func moveViewToY(yValue: CGFloat, axis: ChartYAxis.AxisDependency) + { + var valsInView = getDeltaY(axis) / _viewPortHandler.scaleY; + + var pt = CGPoint(x: 0.0, y: yValue + valsInView / 2.0); + + getTransformer(axis).pointValueToPixel(&pt); + _viewPortHandler.centerViewPort(pt: pt, chart: self); + } + + /// This will move the left side of the current viewport to the specified + /// x-index on the x-axis, and center the viewport to the specified y-value + /// on the y-axis. + /// + /// :param: xIndex + /// :param: yValue + /// :param: axis - which axis should be used as a reference for the y-axis + public func moveViewTo(xIndex: Int, yValue: CGFloat, axis: ChartYAxis.AxisDependency) + { + var valsInView = getDeltaY(axis) / _viewPortHandler.scaleY; + + var pt = CGPoint(x: CGFloat(xIndex), y: yValue + valsInView / 2.0); + + getTransformer(axis).pointValueToPixel(&pt); + _viewPortHandler.centerViewPort(pt: pt, chart: self); + } + + /// Sets custom offsets for the current ViewPort (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. + public func setViewPortOffsets(#left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) + { + _customViewPortEnabled = true; + + if (NSThread.isMainThread()) + { + self._viewPortHandler.restrainViewPort(offsetLeft: left, offsetTop: top, offsetRight: right, offsetBottom: bottom); + } + else + { + dispatch_async(dispatch_get_main_queue(), { + self._viewPortHandler.restrainViewPort(offsetLeft: left, offsetTop: top, offsetRight: right, offsetBottom: bottom); + }); + } + } + + /// Resets all custom offsets set via setViewPortOffsets(...) method. Allows the chart to again calculate all offsets automatically. + public func resetViewPortOffsets() + { + _customViewPortEnabled = false; + calculateOffsets(); + } + + // MARK: - Accessors + + /// Returns the delta-y value (y-value range) of the specified axis. + public func getDeltaY(axis: ChartYAxis.AxisDependency) -> CGFloat + { + if (axis == .Left) + { + return CGFloat(leftAxis.axisRange); + } + else + { + return CGFloat(rightAxis.axisRange); + } + } + + /// Returns the position (in pixels) the provided Entry has inside the chart view + public func getPosition(e: ChartDataEntry, axis: ChartYAxis.AxisDependency) -> CGPoint + { + var vals = CGPoint(x: CGFloat(e.xIndex), y: CGFloat(e.value)); + + getTransformer(axis).pointValueToPixel(&vals); + + return vals; + } + + /// the number of maximum visible drawn values on the chart + /// only active when setDrawValues() is enabled + public var maxVisibleValueCount: Int + { + get + { + return _maxVisibleValueCount; + } + set + { + _maxVisibleValueCount = newValue; + } + } + + /// If set to true, the highlight indicators (cross of two lines for + /// LineChart and ScatterChart, dark bar overlay for BarChart) that give + /// visual indication that an Entry has been selected will be drawn upon + /// selecting values. This does not depend on the MarkerView. + /// :default: true + public var isHighlightIndicatorEnabled: Bool + { + return highlightIndicatorEnabled; + } + + /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). + public var dragEnabled: Bool + { + get + { + return _dragEnabled; + } + set + { + if (_dragEnabled != newValue) + { + _dragEnabled = newValue; + if (_dragEnabled) + { + self.addGestureRecognizer(_panGestureRecognizer); + } + else + { + if (self.gestureRecognizers != nil) + { + for (var i = 0; i < self.gestureRecognizers!.count; i++) + { + if (self.gestureRecognizers?[i] === _panGestureRecognizer) + { + self.gestureRecognizers!.removeAtIndex(i); + break; + } + } + } + } + } + } + } + + /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). + public var isDragEnabled: Bool + { + return dragEnabled; + } + + /// is scaling enabled? (zooming in and out by gesture) for the chart (this does not affect dragging). + public func setScaleEnabled(enabled: Bool) + { + if (_scaleXEnabled != enabled || _scaleYEnabled != enabled) + { + _scaleXEnabled = enabled; + _scaleYEnabled = enabled; + updateScaleGestureRecognizers(); + } + } + + public var scaleXEnabled: Bool + { + get + { + return _scaleXEnabled + } + set + { + if (_scaleXEnabled != newValue) + { + _scaleXEnabled = newValue; + updateScaleGestureRecognizers(); + } + } + } + + public var scaleYEnabled: Bool + { + get + { + return _scaleYEnabled + } + set + { + if (_scaleYEnabled != newValue) + { + _scaleYEnabled = newValue; + updateScaleGestureRecognizers(); + } + } + } + + public var isScaleXEnabled: Bool { return scaleXEnabled; } + public var isScaleYEnabled: Bool { return scaleYEnabled; } + + /// flag that indicates if double tap zoom is enabled or not + public var doubleTapToZoomEnabled: Bool + { + get + { + return _doubleTapToZoomEnabled; + } + set + { + if (_doubleTapToZoomEnabled != newValue) + { + _doubleTapToZoomEnabled = newValue; + if (_doubleTapToZoomEnabled) + { + self.addGestureRecognizer(_doubleTapGestureRecognizer); + } + else + { + if (self.gestureRecognizers != nil) + { + for (var i = 0; i < self.gestureRecognizers!.count; i++) + { + if (self.gestureRecognizers?[i] === _doubleTapGestureRecognizer) + { + self.gestureRecognizers!.removeAtIndex(i); + break; + } + } + } + } + } + } + } + + /// :returns: true if zooming via double-tap is enabled false if not. + /// :default: true + public var isDoubleTapToZoomEnabled: Bool + { + return doubleTapToZoomEnabled; + } + + /// :returns: true if drawing the grid background is enabled, false if not. + /// :default: true + public var isDrawGridBackgroundEnabled: Bool + { + return drawGridBackgroundEnabled; + } + + /// :returns: true if drawing the borders rectangle is enabled, false if not. + /// :default: false + public var isDrawBordersEnabled: Bool + { + return drawBordersEnabled; + } + + /// Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the Line-, Scatter-, or CandleStick-Chart. + public func getHighlightByTouchPoint(var pt: CGPoint) -> ChartHighlight! + { + if (_dataNotSet || _data === nil) + { + println("Can't select by touch. No data set."); + return nil; + } + + var valPt = CGPoint(); + valPt.x = pt.x; + valPt.y = 0.0; + + // take any transformer to determine the x-axis value + _leftAxisTransformer.pixelToValue(&valPt); + + var xTouchVal = valPt.x; + var base = floor(xTouchVal); + + var touchOffset = _deltaX * 0.025; + + // touch out of chart + if (xTouchVal < -touchOffset || xTouchVal > _deltaX + touchOffset) + { + return nil; + } + + if (base < 0.0) + { + base = 0.0; + } + + if (base >= _deltaX) + { + base = _deltaX - 1.0; + } + + var xIndex = Int(base); + + // check if we are more than half of a x-value or not + if (xTouchVal - base > 0.5) + { + xIndex = Int(base + 1.0); + } + + var valsAtIndex = getYValsAtIndex(xIndex); + + var leftdist = ChartUtils.getMinimumDistance(valsAtIndex, val: Float(pt.y), axis: .Left); + var rightdist = ChartUtils.getMinimumDistance(valsAtIndex, val: Float(pt.y), axis: .Right); + + if (_data!.getFirstRight() === nil) + { + rightdist = FLT_MAX; + } + if (_data!.getFirstLeft() === nil) + { + leftdist = FLT_MAX; + } + + var axis: ChartYAxis.AxisDependency = leftdist < rightdist ? .Left : .Right; + + var dataSetIndex = ChartUtils.closestDataSetIndex(valsAtIndex, value: Float(pt.y), axis: axis); + + if (dataSetIndex == -1) + { + return nil; + } + + return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex); + } + + /// Returns an array of SelInfo objects for the given x-index. The SelInfo + /// objects give information about the value at the selected index and the + /// DataSet it belongs to. + public func getYValsAtIndex(xIndex: Int) -> [ChartSelInfo] + { + var vals = [ChartSelInfo](); + + var pt = CGPoint(); + + for (var i = 0, count = _data.dataSetCount; i < count; i++) + { + var dataSet = _data.getDataSetByIndex(i); + if (dataSet === nil) + { + continue; + } + + // extract all y-values from all DataSets at the given x-index + var yVal = dataSet!.yValForXIndex(xIndex); + pt.y = CGFloat(yVal); + + getTransformer(dataSet!.axisDependency).pointValueToPixel(&pt); + + if (!isnan(pt.y)) + { + vals.append(ChartSelInfo(value: Float(pt.y), dataSetIndex: i, dataSet: dataSet!)); + } + } + + return vals; + } + + /// Returns the x and y values in the chart at the given touch point + /// (encapsulated in a PointD). This method transforms pixel coordinates to + /// coordinates / values in the chart. This is the opposite method to + /// getPixelsForValues(...). + public func getValueByTouchPoint(var #pt: CGPoint, axis: ChartYAxis.AxisDependency) -> CGPoint + { + getTransformer(axis).pixelToValue(&pt); + + return pt; + } + + /// Transforms the given chart values into pixels. This is the opposite + /// method to getValueByTouchPoint(...). + public func getPixelForValue(x: Float, y: Float, axis: ChartYAxis.AxisDependency) -> CGPoint + { + var pt = CGPoint(x: CGFloat(x), y: CGFloat(y)); + + getTransformer(axis).pointValueToPixel(&pt); + + return pt; + } + + /// returns the y-value at the given touch position (must not necessarily be + /// a value contained in one of the datasets) + public func getYValueByTouchPoint(#pt: CGPoint, axis: ChartYAxis.AxisDependency) -> CGFloat + { + return getValueByTouchPoint(pt: pt, axis: axis).y; + } + + /// returns the Entry object displayed at the touched position of the chart + public func getEntryByTouchPoint(pt: CGPoint) -> ChartDataEntry! + { + var h = getHighlightByTouchPoint(pt); + if (h !== nil) + { + return _data!.getEntryForHighlight(h!); + } + return nil; + } + + ///returns the DataSet object displayed at the touched position of the chart + public func getDataSetByTouchPoint(pt: CGPoint) -> BarLineScatterCandleChartDataSet! + { + var h = getHighlightByTouchPoint(pt); + if (h !== nil) + { + return _data.getDataSetByIndex(h.dataSetIndex) as! BarLineScatterCandleChartDataSet!; + } + return nil; + } + + /// Returns the lowest x-index (value on the x-axis) that is still visible on he chart. + public var lowestVisibleXIndex: Int + { + var pt = CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom); + getTransformer(.Left).pixelToValue(&pt); + return (pt.x <= 0.0) ? 0 : Int(pt.x + 1.0); + } + + /// Returns the highest x-index (value on the x-axis) that is still visible on the chart. + public var highestVisibleXIndex: Int + { + var pt = CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom); + getTransformer(.Left).pixelToValue(&pt); + return (Int(pt.x) >= _data.xValCount) ? _data.xValCount - 1 : Int(pt.x); + } + + /// returns the current x-scale factor + public var scaleX: CGFloat { return _viewPortHandler.scaleX; } + + /// returns the current y-scale factor + public var scaleY: CGFloat { return _viewPortHandler.scaleY; } + + /// if the chart is fully zoomed out, return true + public var isFullyZoomedOut: Bool { return _viewPortHandler.isFullyZoomedOut; } + + /// Returns the left y-axis object. In the horizontal bar-chart, this is the + /// top axis. + public var leftAxis: ChartYAxis + { + return _leftAxis; + } + + /// Returns the right y-axis object. In the horizontal bar-chart, this is the + /// bottom axis. + public var rightAxis: ChartYAxis { return _rightAxis; } + + /// Returns the y-axis object to the corresponding AxisDependency. In the + /// horizontal bar-chart, LEFT == top, RIGHT == BOTTOM + public func getAxis(axis: ChartYAxis.AxisDependency) -> ChartYAxis + { + if (axis == .Left) + { + return _leftAxis; + } + else + { + return _rightAxis; + } + } + + /// Returns the object representing all x-labels, this method can be used to + /// acquire the XAxis object and modify it (e.g. change the position of the + /// labels) + public var xAxis: ChartXAxis + { + return _xAxis; + } + + /// flag that indicates if pinch-zoom is enabled. if true, both x and y axis can be scaled with 2 fingers, if false, x and y axis can be scaled separately + public var pinchZoomEnabled: Bool + { + get + { + return _pinchZoomEnabled; + } + set + { + if (_pinchZoomEnabled != newValue) + { + _pinchZoomEnabled = newValue; + updateScaleGestureRecognizers(); + } + } + } + + private func updateScaleGestureRecognizers() + { + if (self.gestureRecognizers != nil) + { + for (var i = 0; i < self.gestureRecognizers!.count; i++) + { + if (self.gestureRecognizers![i] === _pinchGestureRecognizer) + { + self.gestureRecognizers!.removeAtIndex(i); + break; + } + } + } + + if (_pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled) + { + self.addGestureRecognizer(_pinchGestureRecognizer); + } + } + + /// returns true if pinch-zoom is enabled, false if not + /// :default: false + public var isPinchZoomEnabled: Bool { return pinchZoomEnabled; } + + /// Set an offset in dp that allows the user to drag the chart over it's + /// bounds on the x-axis. + public func setDragOffsetX(offset: CGFloat) + { + _viewPortHandler.setDragOffsetX(offset); + } + + /// Set an offset in dp that allows the user to drag the chart over it's + /// bounds on the y-axis. + public func setDragOffsetY(offset: CGFloat) + { + _viewPortHandler.setDragOffsetY(offset); + } + + /// :returns: true if both drag offsets (x and y) are zero or smaller. + public var hasNoDragOffset: Bool { return _viewPortHandler.hasNoDragOffset; } + + public var xAxisRenderer: ChartXAxisRenderer { return _xAxisRenderer; } + + public var leftYAxisRenderer: ChartYAxisRenderer { return _leftYAxisRenderer; } + + public var rightYAxisRenderer: ChartYAxisRenderer { return _rightYAxisRenderer; } + + public override var chartYMax: Float + { + return max(leftAxis.axisMaximum, rightAxis.axisMaximum); + } + + public override var chartYMin: Float + { + return min(leftAxis.axisMinimum, rightAxis.axisMinimum); + } + + /// Returns true if either the left or the right or both axes are inverted. + public var isAnyAxisInverted: Bool + { + return _leftAxis.isInverted || _rightAxis.isInverted; + } +} + +/// Default formatter that calculates the position of the filled line. +internal class BarLineChartFillFormatter: NSObject, ChartFillFormatter +{ + private weak var _chart: BarLineChartViewBase!; + + internal init(chart: BarLineChartViewBase) + { + _chart = chart; + } + + internal func getFillLinePosition(#dataSet: LineChartDataSet, data: LineChartData, chartMaxY: Float, chartMinY: Float) -> CGFloat + { + var fillMin = CGFloat(0.0); + + if (dataSet.yMax > 0.0 && dataSet.yMin < 0.0) + { + fillMin = 0.0; + } + else + { + if (!_chart.getAxis(dataSet.axisDependency).isStartAtZeroEnabled) + { + var max: Float, min: Float; + + if (data.yMax > 0.0) + { + max = 0.0; + } + else + { + max = chartMaxY; + } + + if (data.yMin < 0.0) + { + min = 0.0; + } + else + { + min = chartMinY; + } + + fillMin = CGFloat(dataSet.yMin >= 0.0 ? min : max); + } + else + { + fillMin = 0.0; + } + } + + return fillMin; + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/CandleStickChartView.swift b/Charts/Classes/Charts/CandleStickChartView.swift new file mode 100644 index 0000000000..03642d8ef5 --- /dev/null +++ b/Charts/Classes/Charts/CandleStickChartView.swift @@ -0,0 +1,76 @@ +// +// CandleStickChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Financial chart type that draws candle-sticks. +public class CandleStickChartView: BarLineChartViewBase, CandleStickChartRendererDelegate +{ + internal override func initialize() + { + super.initialize(); + + renderer = CandleStickChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler); + _chartXMin = -0.5; + } + + internal override func calcMinMax() + { + super.calcMinMax(); + + _chartXMax += 0.5; + _deltaX = CGFloat(abs(_chartXMax - _chartXMin)); + } + + // MARK: - CandleStickChartRendererDelegate + + public func candleStickChartRendererCandleData(renderer: CandleStickChartRenderer) -> CandleChartData! + { + return _data as! CandleChartData!; + } + + public func candleStickChartRenderer(renderer: CandleStickChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return self.getTransformer(which); + } + + public func candleStickChartDefaultRendererValueFormatter(renderer: CandleStickChartRenderer) -> NSNumberFormatter! + { + return self.valueFormatter; + } + + public func candleStickChartRendererChartYMax(renderer: CandleStickChartRenderer) -> Float + { + return self.chartYMax; + } + + public func candleStickChartRendererChartYMin(renderer: CandleStickChartRenderer) -> Float + { + return self.chartYMin; + } + + public func candleStickChartRendererChartXMax(renderer: CandleStickChartRenderer) -> Float + { + return self.chartXMax; + } + + public func candleStickChartRendererChartXMin(renderer: CandleStickChartRenderer) -> Float + { + return self.chartXMin; + } + + public func candleStickChartRendererMaxVisibleValueCount(renderer: CandleStickChartRenderer) -> Int + { + return self.maxVisibleValueCount; + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/ChartViewBase.swift b/Charts/Classes/Charts/ChartViewBase.swift new file mode 100644 index 0000000000..1affadb9c3 --- /dev/null +++ b/Charts/Classes/Charts/ChartViewBase.swift @@ -0,0 +1,738 @@ +// +// ChartViewBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// +// Based on https://github.com/PhilJay/MPAndroidChart/commit/c42b880 + +import Foundation +import UIKit; + +@objc +public protocol ChartViewDelegate +{ + /// Called when a value has been selected inside the chart. + /// :entry: The selected Entry. + /// :dataSetIndex: The index in the datasets array of the data object the Entrys DataSet is in. + optional func chartValueSelected(chartView: ChartViewBase, entry: ChartDataEntry, dataSetIndex: Int, highlight: ChartHighlight); + + // Called when nothing has been selected or an "un-select" has been made. + optional func chartValueNothingSelected(chartView: ChartViewBase); +} + +public class ChartViewBase: UIView, ChartAnimatorDelegate +{ + // MARK: - Properties + + /// custom formatter that is used instead of the auto-formatter if set + internal var _valueFormatter = NSNumberFormatter() + + /// the default value formatter + internal var _defaultValueFormatter = NSNumberFormatter() + + /// object that holds all data that was originally set for the chart, before it was modified or any filtering algorithms had been applied + internal var _data: ChartData! + + /// font object used for drawing the description text in the bottom right corner of the chart + public var descriptionFont: UIFont? = UIFont(name: "HelveticaNeue", size: 9.0) + internal var _descriptionTextColor: UIColor! = UIColor.blackColor() + + /// font object for drawing the information text when there are no values in the chart + internal var _infoFont: UIFont! = UIFont(name: "HelveticaNeue", size: 12.0) + internal var _infoTextColor: UIColor! = UIColor(red: 247.0/255.0, green: 189.0/255.0, blue: 51.0/255.0, alpha: 1.0) // orange + + /// description text that appears in the bottom right corner of the chart + public var descriptionText = "Description" + + /// flag that indicates if the chart has been fed with data yet + internal var _dataNotSet = true + + /// if true, units are drawn next to the values in the chart + internal var _drawUnitInChart = false + + /// the number of x-values the chart displays + internal var _deltaX = CGFloat(1.0) + + internal var _chartXMin = Float(0.0) + internal var _chartXMax = Float(0.0) + + /// if true, value highlightning is enabled + public var highlightEnabled = true + + /// the legend object containing all data associated with the legend + internal var _legend: ChartLegend! = ChartLegend(); + + /// delegate to receive chart events + public weak var delegate: ChartViewDelegate? + + /// text that is displayed when the chart is empty + public var noDataText = "No chart data available." + + /// text that is displayed when the chart is empty that describes why the chart is empty + public var noDataTextDescription: String? + + internal var _legendRenderer: ChartLegendRenderer! + + /// object responsible for rendering the data + public var renderer: ChartDataRendererBase? + + /// object that manages the bounds and drawing constraints of the chart + internal var _viewPortHandler: ChartViewPortHandler! + + /// object responsible for animations + internal var _animator: ChartAnimator! + + /// flag that indicates if offsets calculation has already been done or not + private var _offsetsCalculated = false + + /// array of Highlight objects that reference the highlighted slices in the chart + internal var _indicesToHightlight = [ChartHighlight]() + + /// if set to true, the marker is drawn when a value is clicked + public var drawMarkers = true + + /// the view that represents the marker + public var marker: ChartMarker? + + private var _interceptTouchEvents = false + + // MARK: - Initializers + + public override init(frame: CGRect) + { + super.init(frame: frame); + initialize(); + } + + public required init(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder); + initialize(); + } + + internal func initialize() + { + _animator = ChartAnimator(); + _animator.delegate = self; + + _viewPortHandler = ChartViewPortHandler(); + _viewPortHandler.setChartDimens(width: bounds.size.width, height: bounds.size.height); + _legendRenderer = ChartLegendRenderer(viewPortHandler: _viewPortHandler); + + _defaultValueFormatter.maximumFractionDigits = 1; + _defaultValueFormatter.minimumFractionDigits = 1; + _defaultValueFormatter.usesGroupingSeparator = true; + + _valueFormatter = _defaultValueFormatter.copy() as! NSNumberFormatter; + } + + // MARK: - ChartViewBase + + /// The data for the chart + public var data: ChartData? + { + get + { + return _data; + } + set + { + if (newValue == nil) + { + println("Charts: data argument is nil on setData()"); + return; + } + + _dataNotSet = false; + _offsetsCalculated = false; + _data = newValue; + + // calculate how many digits are needed + calculateFormatter(min: _data.getYMin(), max: _data.getYMax()); + + notifyDataSetChanged(); + } + } + + /// Clears the chart from all data (sets it to null) and refreshes it (by calling setNeedsDisplay()). + public func clear() + { + _data = nil; + _dataNotSet = true; + setNeedsDisplay(); + } + + /// Removes all DataSets (and thereby Entries) from the chart. Does not remove the x-values. Also refreshes the chart by calling setNeedsDisplay(). + public func clearValues() + { + if (_data !== nil) + { + _data.clearValues(); + } + setNeedsDisplay(); + } + + /// Returns true if the chart is empty (meaning it's data object is either null or contains no entries). + public func isEmpty() -> Bool + { + if (_data == nil) + { + return true; + } + else + { + + if (_data.yValCount <= 0) + { + return true; + } + else + { + return false; + } + } + } + + /// Lets the chart know its underlying data has changed and should perform all necessary recalculations. + public func notifyDataSetChanged() + { + fatalError("notifyDataSetChanged() cannot be called on ChartViewBase"); + } + + /// 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 + internal func calculateOffsets() + { + fatalError("calculateOffsets() cannot be called on ChartViewBase"); + } + + /// calcualtes the y-min and y-max value and the y-delta and x-delta value + internal func calcMinMax() + { + fatalError("calcMinMax() cannot be called on ChartViewBase"); + } + + /// calculates the required number of digits for the values that might be drawn in the chart (if enabled), and creates the default value formatter + internal func calculateFormatter(#min: Float, max: Float) + { + // check if a custom formatter is set or not + var reference = Float(0.0); + + if (_data == nil || _data.xValCount < 2) + { + var absMin = fabs(min); + var absMax = fabs(max); + reference = absMin > absMax ? absMin : absMax; + } + else + { + reference = fabs(max - min); + } + + var digits = ChartUtils.decimals(reference); + + _defaultValueFormatter.maximumFractionDigits = digits; + _defaultValueFormatter.minimumFractionDigits = digits; + } + + public override func drawRect(rect: CGRect) + { + let context = UIGraphicsGetCurrentContext(); + let frame = self.bounds; + + if (_dataNotSet) + { // check if there is data + + CGContextSaveGState(context); + + // if no data, inform the user + + ChartUtils.drawText(context: context, text: noDataText, point: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0), align: .Center, attributes: [NSFontAttributeName: _infoFont, NSForegroundColorAttributeName: _infoTextColor]); + + if (noDataTextDescription!.lengthOfBytesUsingEncoding(NSUTF16StringEncoding) > 0) + { + var textOffset = -_infoFont.lineHeight / 2.0; + + ChartUtils.drawText(context: context, text: noDataTextDescription!, point: CGPoint(x: frame.width / 2.0, y: frame.height / 2.0 + textOffset), align: .Center, attributes: [NSFontAttributeName: _infoFont, NSForegroundColorAttributeName: _infoTextColor]); + } + + return; + } + + if (!_offsetsCalculated) + { + calculateOffsets(); + _offsetsCalculated = true; + } + } + + /// draws the description text in the bottom right corner of the chart + internal func drawDescription(#context: CGContext) + { + if (descriptionText.lengthOfBytesUsingEncoding(NSUTF16StringEncoding) == 0) + { + return; + } + + let frame = self.bounds; + + var attrs = [NSObject: AnyObject](); + + var font = descriptionFont; + + if (font == nil) + { + font = UIFont.systemFontOfSize(UIFont.systemFontSize()); + } + + attrs[NSFontAttributeName] = font; + attrs[NSForegroundColorAttributeName] = UIColor.blackColor(); + + ChartUtils.drawText(context: context, text: descriptionText, point: CGPoint(x: frame.width - _viewPortHandler.offsetRight - 10.0, y: frame.height - _viewPortHandler.offsetBottom - 10.0 - font!.lineHeight), align: .Right, attributes: attrs); + } + + /// disables intercept touchevents + public func disableScroll() + { + _interceptTouchEvents = true; + } + + /// enables intercept touchevents + public func enableScroll() + { + _interceptTouchEvents = false; + } + + // MARK: - Highlighting + + /// Returns the array of currently highlighted values. This might be null or empty if nothing is highlighted. + public var highlighted: [ChartHighlight] + { + return _indicesToHightlight; + } + + /// Returns true if there are values to highlight, + /// false if there are no values to highlight. + /// Checks if the highlight array is null, has a length of zero or if the first object is null. + public func valuesToHighlight() -> Bool + { + return _indicesToHightlight.count > 0; + } + + /// Highlights the values at the given indices in the given DataSets. Provide + /// null or an empty array to undo all highlighting. + /// This should be used to programmatically highlight values. + /// This DOES NOT generate a callback to the delegate. + public func highlightValues(highs: [ChartHighlight]?) + { + // set the indices to highlight + _indicesToHightlight = highs ?? [ChartHighlight](); + + // redraw the chart + setNeedsDisplay(); + } + + /// Highlights the value at the given x-index in the given DataSet. + /// Provide -1 as the x-index to undo all highlighting. + public func highlightValue(#xIndex: Int, dataSetIndex: Int, callDelegate: Bool) + { + if (xIndex < 0 || dataSetIndex < 0 || xIndex >= _data.xValCount || dataSetIndex >= _data.dataSetCount) + { + highlightValue(highlight: nil, callDelegate: callDelegate); + } + else + { + highlightValue(highlight: ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex), callDelegate: callDelegate); + } + } + + /// Highlights the value selected by touch gesture. + public func highlightValue(#highlight: ChartHighlight?, callDelegate: Bool) + { + if (highlight == nil) + { + _indicesToHightlight.removeAll(keepCapacity: false); + } + else + { + // set the indices to highlight + _indicesToHightlight = [highlight!]; + } + + // redraw the chart + setNeedsDisplay(); + + if (callDelegate && delegate != nil) + { + if (highlight == nil) + { + delegate!.chartValueNothingSelected!(self); + } + else + { + var e = _data.getEntryForHighlight(highlight!); + + // notify the listener + delegate!.chartValueSelected!(self, entry: e, dataSetIndex: highlight!.dataSetIndex, highlight: highlight!); + } + } + } + + // MARK: - Markers + + /// draws all MarkerViews on the highlighted positions + internal func drawMarkers(#context: CGContext) + { + // if there is no marker view or drawing marker is disabled + if (marker === nil || !drawMarkers || !valuesToHighlight()) + { + return; + } + + for (var i = 0, count = _indicesToHightlight.count; i < count; i++) + { + let highlight = _indicesToHightlight[i]; + let xIndex = highlight.xIndex; + let dataSetIndex = highlight.dataSetIndex; + + if (xIndex <= Int(_deltaX) && xIndex <= Int(_deltaX * _animator.phaseX)) + { + let e = _data.getEntryForHighlight(highlight); + + var pos = getMarkerPosition(entry: e, dataSetIndex: dataSetIndex); + + // check bounds + if (!_viewPortHandler.isInBounds(x: pos.x, y: pos.y)) + { + continue; + } + + // callbacks to update the content + marker!.refreshContent(entry: e, dataSetIndex: dataSetIndex); + + let markerSize = marker!.size; + if (pos.y - markerSize.height <= 0.0) + { + let y = markerSize.height - pos.y; + marker!.draw(context: context, point: CGPoint(x: pos.x, y: pos.y + y)); + } + else + { + marker!.draw(context: context, point: pos); + } + } + } + } + + /// Returns the actual position in pixels of the MarkerView for the given Entry in the given DataSet. + public func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + { + fatalError("getMarkerPosition() cannot be called on ChartViewBase"); + } + + // MARK: - Animation + + /// Returns the animator responsible for animating chart values. + public var animator: ChartAnimator! + { + return _animator; + } + + /// Animates the drawing / rendering of the chart on both x- and y-axis with + /// the specified animation time. + /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. + public func animateXY(#durationX: NSTimeInterval, durationY: NSTimeInterval) + { + _animator.animateXY(durationX: durationX, durationY: durationY); + } + + /// Animates the drawing / rendering of the chart the x-axis with + /// the specified animation time. + /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. + public func animateX(#duration: NSTimeInterval) + { + _animator.animateX(duration: duration); + } + + /// Animates the drawing / rendering of the chart the y-axis with + /// the specified animation time. + /// If animate(...) is called, no further calling of invalidate() is necessary to refresh the chart. + public func animateY(#duration: NSTimeInterval) + { + _animator.animateY(duration: duration); + } + + // MARK: - Accessors + + /// returns the total value (sum) of all y-values across all DataSets + public var yValueSum: Float + { + return _data.yValueSum; + } + + /// returns the current y-max value across all DataSets + public var chartYMax: Float + { + return _data.yMax; + } + + /// returns the current y-min value across all DataSets + public var chartYMin: Float + { + return _data.yMin; + } + + public var chartXMax: Float + { + return _chartXMax; + } + + public var chartXMin: Float + { + return _chartXMin; + } + + /// returns the average value of all values the chart holds + public func getAverage() -> Float + { + return yValueSum / Float(_data.yValCount); + } + + /// returns the average value for a specific DataSet (with a specific label) in the chart + public func getAverage(#dataSetLabel: String) -> Float + { + var ds = _data.getDataSetByLabel(dataSetLabel, ignorecase: true); + if (ds == nil) + { + return 0.0; + } + + return ds!.yValueSum / Float(ds!.entryCount); + } + + /// returns the total number of values the chart holds (across all DataSets) + public var getValueCount: Int + { + return _data.yValCount; + } + + /// Returns the center of the chart taking offsets under consideration. (returns the center of the content rectangle) + public var centerOffsets: CGPoint + { + return _viewPortHandler.contentCenter; + } + + /// Returns the Legend object of the chart. + /// This property can be used to customize the automatically generated Legend. + /// IMPORTANT: Since the Legend is generated from data provided by the user (via setData(...) method), + /// this will return nil if no data has been set for the chart. + /// You need to set data for the chart before calling this method. + public var legend: ChartLegend + { + return _legend; + } + + /// Returns the renderer object responsible for rendering / drawing the Legend. + public var legendRenderer: ChartLegendRenderer! + { + return _legendRenderer; + } + + /// Returns the rectangle that defines the borders of the chart-value surface (into which the actual values are drawn). + public var contentRect: CGRect + { + return _viewPortHandler.contentRect; + } + + /// Sets the formatter to be used for drawing the values inside the chart. + /// 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. Set this to nil to re-enable auto formatting. + public var valueFormatter: NSNumberFormatter! + { + get + { + return _valueFormatter; + } + set + { + if (newValue === nil) + { + _valueFormatter = _defaultValueFormatter.copy() as! NSNumberFormatter; + } + else + { + _valueFormatter = newValue; + } + } + } + + /// returns the x-value at the given index + public func getXValue(index: Int) -> String! + { + if (_data == nil || _data.xValCount <= index) + { + return nil; + } + else + { + return _data.xVals[index]; + } + } + + /// Get all Entry objects at the given index across all DataSets. + public func getEntriesAtIndex(xIndex: Int) -> [ChartDataEntry] + { + var vals = [ChartDataEntry](); + + for (var i = 0, count = _data.dataSetCount; i < count; i++) + { + var set = _data.getDataSetByIndex(i); + var e = set!.entryForXIndex(xIndex); + if (e !== nil) + { + vals.append(e); + } + } + + return vals; + } + + /// returns the percentage the given value has of the total y-value sum + public func percentOfTotal(val: Float) -> Float + { + return val / _data.yValueSum * 100.0; + } + + /// Returns the ViewPortHandler of the chart that is responsible for the + /// content area of the chart and its offsets and dimensions. + public var viewPortHandler: ChartViewPortHandler! + { + return _viewPortHandler; + } + + /// Returns the bitmap that represents the chart. + public func getChartImage() -> UIImage + { + UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, UIScreen.mainScreen().scale); + + layer.renderInContext(UIGraphicsGetCurrentContext()); + + var image = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + return image; + } + + public enum ImageFormat + { + case JPEG; + case PNG; + } + + /// Saves the current chart state with the given name to the given path on + /// the sdcard leaving the path empty "" will put the saved file directly on + /// the SD card chart is saved as a PNG image, example: + /// saveToPath("myfilename", "foldername1/foldername2"); + /// + /// :filePath: path to the image to save + /// :format: the format to save + /// :compressionQuality: compression quality for lossless formats (JPEG) + /// + /// :returns: true if the image was saved successfully + public func saveToPath(path: String, format: ImageFormat, compressionQuality: Float) -> Bool + { + var image = getChartImage(); + + var imageData: NSData!; + switch (format) + { + case .PNG: + imageData = UIImagePNGRepresentation(image); + break; + + case .JPEG: + imageData = UIImageJPEGRepresentation(image, CGFloat(compressionQuality)); + break; + } + + return imageData.writeToFile(path, atomically: true); + } + + /// Saves the current state of the chart to the camera roll + public func saveToCameraRoll() + { + UIImageWriteToSavedPhotosAlbum(getChartImage(), nil, nil, nil); + } + + public override var bounds: CGRect + { + get + { + return super.bounds; + } + set + { + super.bounds = newValue; + + if (_viewPortHandler !== nil) + { + _viewPortHandler.setChartDimens(width: newValue.size.width, height: newValue.size.height); + } + + notifyDataSetChanged(); + } + } + + /// if true, value highlightning is enabled + public var isHighlightEnabled: Bool { return highlightEnabled; } + + // MARK: - ChartAnimatorDelegate + + public func chartAnimatorUpdated(chartAnimator: ChartAnimator) + { + setNeedsDisplay(); + } + + // MARK: - Touches + + public override func touchesBegan(touches: Set, withEvent event: UIEvent) + { + if (!_interceptTouchEvents) + { + super.touchesBegan(touches, withEvent: event); + } + } + + public override func touchesMoved(touches: Set, withEvent event: UIEvent) + { + if (!_interceptTouchEvents) + { + super.touchesMoved(touches, withEvent: event); + } + } + + public override func touchesEnded(touches: Set, withEvent event: UIEvent) + { + if (!_interceptTouchEvents) + { + super.touchesEnded(touches, withEvent: event); + } + } + + public override func touchesCancelled(touches: Set, withEvent event: UIEvent) + { + if (!_interceptTouchEvents) + { + super.touchesCancelled(touches, withEvent: event); + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/CombinedChartView.swift b/Charts/Classes/Charts/CombinedChartView.swift new file mode 100644 index 0000000000..eb48ebcf28 --- /dev/null +++ b/Charts/Classes/Charts/CombinedChartView.swift @@ -0,0 +1,186 @@ +// +// CombinedChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// This chart class allows the combination of lines, bars, scatter and candle data all displayed in one chart area. +public class CombinedChartView: BarLineChartViewBase +{ + /// the fill-formatter used for determining the position of the fill-line + internal var _fillFormatter: ChartFillFormatter! + + /// enum that allows to specify the order in which the different data objects for the combined-chart are drawn + @objc + public enum DrawOrder: Int + { + case Bar + case Line + case Candle + case Scatter + } + + public override func initialize() + { + super.initialize(); + + _fillFormatter = BarLineChartFillFormatter(chart: self); + + renderer = CombinedChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler); + } + + override func calcMinMax() + { + super.calcMinMax(); + + if (self.barData !== nil || self.candleData !== nil) + { + _chartXMin = -0.5; + _chartXMax = Float(_data.xVals.count) - 0.5; + _deltaX = CGFloat(abs(_chartXMax - _chartXMin)); + } + } + + public override var data: ChartData? + { + get + { + return super.data; + } + set + { + super.data = newValue; + (renderer as! CombinedChartRenderer?)?.createRenderers(); + } + } + + public var fillFormatter: ChartFillFormatter + { + get + { + return _fillFormatter; + } + set + { + _fillFormatter = newValue; + if (_fillFormatter === nil) + { + _fillFormatter = BarLineChartFillFormatter(chart: self); + } + } + } + + public var lineData: LineChartData! + { + get + { + if (_data === nil) + { + return nil; + } + return (_data as! CombinedChartData!).lineData; + } + } + + public var barData: BarChartData! + { + get + { + if (_data === nil) + { + return nil; + } + return (_data as! CombinedChartData!).barData; + } + } + + public var scatterData: ScatterChartData! + { + get + { + if (_data === nil) + { + return nil; + } + return (_data as! CombinedChartData!).scatterData; + } + } + + public var candleData: CandleChartData! + { + get + { + if (_data === nil) + { + return nil; + } + return (_data as! CombinedChartData!).candleData; + } + } + + // MARK: Accessors + + /// flag that enables or disables the highlighting arrow + public var drawHighlightArrowEnabled: Bool + { + get { return (renderer as! CombinedChartRenderer!).drawHighlightArrowEnabled; } + set { (renderer as! CombinedChartRenderer!).drawHighlightArrowEnabled = newValue; } + } + + /// if set to true, all values are drawn above their bars, instead of below their top + public var drawValueAboveBarEnabled: Bool + { + get { return (renderer as! CombinedChartRenderer!).drawValueAboveBarEnabled; } + set { (renderer as! CombinedChartRenderer!).drawValueAboveBarEnabled = newValue; } + } + + /// if set to true, all values of a stack are drawn individually, and not just their sum + public var drawValuesForWholeStackEnabled: Bool + { + get { return (renderer as! CombinedChartRenderer!).drawValuesForWholeStackEnabled; } + set { (renderer as! CombinedChartRenderer!).drawValuesForWholeStackEnabled = newValue; } + } + + /// if set to true, a grey area is darawn behind each bar that indicates the maximum value + public var drawBarShadowEnabled: Bool + { + get { return (renderer as! CombinedChartRenderer!).drawBarShadowEnabled; } + set { (renderer as! CombinedChartRenderer!).drawBarShadowEnabled = newValue; } + } + + /// returns true if drawing the highlighting arrow is enabled, false if not + public var isDrawHighlightArrowEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawHighlightArrowEnabled; } + + /// returns true if drawing values above bars is enabled, false if not + public var isDrawValueAboveBarEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawValueAboveBarEnabled; } + + /// returns true if all values of a stack are drawn, and not just their sum + public var isDrawValuesForWholeStackEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawValuesForWholeStackEnabled; } + + /// returns true if drawing shadows (maxvalue) for each bar is enabled, false if not + public var isDrawBarShadowEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawBarShadowEnabled; } + + /// the order in which the provided data objects should be drawn. + /// The earlier you place them in the provided array, the further they will be in the background. + /// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines. + public var drawOrder: [Int] + { + get + { + return (renderer as! CombinedChartRenderer!).drawOrder.map { $0.rawValue }; + } + set + { + (renderer as! CombinedChartRenderer!).drawOrder = newValue.map { DrawOrder(rawValue: $0)! }; + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/HorizontalBarChartView.swift b/Charts/Classes/Charts/HorizontalBarChartView.swift new file mode 100644 index 0000000000..762b32d236 --- /dev/null +++ b/Charts/Classes/Charts/HorizontalBarChartView.swift @@ -0,0 +1,164 @@ +// +// HorizontalBarChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched. +public class HorizontalBarChartView: BarChartView +{ + internal override func initialize() + { + super.initialize(); + + renderer = HorizontalBarChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler); + _leftYAxisRenderer = ChartYAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: _leftAxis, transformer: _leftAxisTransformer); + _rightYAxisRenderer = ChartYAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: _rightAxis, transformer: _rightAxisTransformer); + _xAxisRenderer = ChartXAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer, chart: self); + } + + internal override func calculateOffsets() + { + var offsetLeft: CGFloat = 0.0, + offsetRight: CGFloat = 0.0, + offsetTop: CGFloat = 0.0, + offsetBottom: CGFloat = 0.0; + + // setup offsets for legend + if (_legend !== nil && _legend.isEnabled) + { + if (_legend.position == .RightOfChart + || _legend.position == .RightOfChartCenter) + { + offsetRight += _legend.textWidthMax + _legend.xOffset * 2.0; + } + else if (_legend.position == .BelowChartLeft + || _legend.position == .BelowChartRight + || _legend.position == .BelowChartCenter) + { + + offsetBottom += _legend.textHeightMax * 3.0; + } + } + + // offsets for y-labels + if (_leftAxis.needsOffset) + { + offsetTop += _leftAxis.requiredSize().height; + } + + if (_rightAxis.needsOffset) + { + offsetBottom += _rightAxis.requiredSize().height; + } + + var xlabelwidth = _xAxis.labelWidth; + + if (_xAxis.isEnabled) + { + // offsets for x-labels + if (_xAxis.labelPosition == .Bottom) + { + offsetLeft += xlabelwidth; + } + else if (_xAxis.labelPosition == .Top) + { + offsetRight += xlabelwidth; + } + else if (_xAxis.labelPosition == .BothSided) + { + offsetLeft += xlabelwidth; + offsetRight += xlabelwidth; + } + } + + var min: CGFloat = 10.0; + + _viewPortHandler.restrainViewPort( + offsetLeft: max(min, offsetLeft), + offsetTop: max(min, offsetTop), + offsetRight: max(min, offsetRight), + offsetBottom: max(min, offsetBottom)); + + prepareOffsetMatrix(); + prepareValuePxMatrix(); + } + + internal override func prepareValuePxMatrix() + { + _rightAxisTransformer.prepareMatrixValuePx(chartXMin: _rightAxis.axisMinimum, deltaX: CGFloat(_rightAxis.axisRange), deltaY: _deltaX, chartYMin: _chartXMin); + _leftAxisTransformer.prepareMatrixValuePx(chartXMin: _leftAxis.axisMinimum, deltaX: CGFloat(_leftAxis.axisRange), deltaY: _deltaX, chartYMin: _chartXMin); + } + + internal override func calcModulus() + { + _xAxis.axisLabelModulus = Int(ceil((CGFloat(_data.xValCount) * _xAxis.labelHeight) / (_viewPortHandler.contentHeight * viewPortHandler.touchMatrix.d))); + + if (_xAxis.axisLabelModulus < 1) + { + _xAxis.axisLabelModulus = 1; + } + } + + public override func getBarBounds(e: BarChartDataEntry) -> CGRect! + { + var set = _data.getDataSetForEntry(e) as! BarChartDataSet!; + + if (set === nil) + { + return nil; + } + + var barspace = set.barSpace; + var y = CGFloat(e.value); + var x = CGFloat(e.xIndex); + + var spaceHalf = barspace / 2.0; + var top = x - 0.5 + spaceHalf; + var bottom = x + 0.5 - spaceHalf; + var left = y >= 0.0 ? y : 0.0; + var right = y <= 0.0 ? y : 0.0; + + var bounds = CGRect(x: left, y: top, width: right - left, height: bottom - top); + + getTransformer(set.axisDependency).rectValueToPixel(&bounds); + + return bounds; + } + + public override func getPosition(e: ChartDataEntry, axis: ChartYAxis.AxisDependency) -> CGPoint + { + var vals = CGPoint(x: CGFloat(e.value), y: CGFloat(e.xIndex)); + + getTransformer(axis).pointValueToPixel(&vals); + + return vals; + } + + public override func getHighlightByTouchPoint(var pt: CGPoint) -> ChartHighlight! + { + if (_dataNotSet || _data === nil) + { + println("Can't select by touch. No data set."); + return nil; + } + + _leftAxisTransformer.pixelToValue(&pt); + + if (pt.y < CGFloat(_chartXMin) || pt.y > CGFloat(_chartXMax)) + { + return nil; + } + + return getHighlight(xPosition: pt.y, yPosition: pt.x); + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/LineChartView.swift b/Charts/Classes/Charts/LineChartView.swift new file mode 100644 index 0000000000..8603d803b9 --- /dev/null +++ b/Charts/Classes/Charts/LineChartView.swift @@ -0,0 +1,109 @@ +// +// LineChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Chart that draws lines, surfaces, circles, ... +public class LineChartView: BarLineChartViewBase, LineChartRendererDelegate +{ + /// the width of the highlighning line + /// :default: 3.0 + internal var highlightWidth: CGFloat = 3.0 + + private var _fillFormatter: ChartFillFormatter! + + internal override func initialize() + { + super.initialize(); + + renderer = LineChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler); + + _fillFormatter = BarLineChartFillFormatter(chart: self); + } + + internal override func calcMinMax() + { + super.calcMinMax(); + + if (_deltaX == 0.0 && _data.yValCount > 0) + { + _deltaX = 1.0; + } + } + + public var fillFormatter: ChartFillFormatter! + { + get + { + return _fillFormatter; + } + set + { + if (newValue === nil) + { + _fillFormatter = BarLineChartFillFormatter(chart: self); + } + else + { + _fillFormatter = newValue; + } + } + } + + // MARK: - LineChartRendererDelegate + + public func lineChartRendererData(renderer: LineChartRenderer) -> LineChartData! + { + return _data as! LineChartData!; + } + + public func lineChartRenderer(renderer: LineChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return self.getTransformer(which); + } + + public func lineChartRendererFillFormatter(renderer: LineChartRenderer) -> ChartFillFormatter + { + return self.fillFormatter; + } + + public func lineChartDefaultRendererValueFormatter(renderer: LineChartRenderer) -> NSNumberFormatter! + { + return self._defaultValueFormatter; + } + + public func lineChartRendererChartYMax(renderer: LineChartRenderer) -> Float + { + return self.chartYMax; + } + + public func lineChartRendererChartYMin(renderer: LineChartRenderer) -> Float + { + return self.chartYMin; + } + + public func lineChartRendererChartXMax(renderer: LineChartRenderer) -> Float + { + return self.chartXMax; + } + + public func lineChartRendererChartXMin(renderer: LineChartRenderer) -> Float + { + return self.chartXMin; + } + + public func lineChartRendererMaxVisibleValueCount(renderer: LineChartRenderer) -> Int + { + return self.maxVisibleValueCount; + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/PieChartView.swift b/Charts/Classes/Charts/PieChartView.swift new file mode 100644 index 0000000000..df00f0536b --- /dev/null +++ b/Charts/Classes/Charts/PieChartView.swift @@ -0,0 +1,442 @@ +// +// PieChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// View that represents a pie chart. Draws cake like slices. +public class PieChartView: PieRadarChartViewBase +{ + /// rect object that represents the bounds of the piechart, needed for drawing the circle + private var _circleBox = CGRect() + + /// array that holds the width of each pie-slice in degrees + private var _drawAngles = [CGFloat]() + + /// array that holds the absolute angle in degrees of each slice + private var _absoluteAngles = [CGFloat]() + + public override init(frame: CGRect) + { + super.init(frame: frame); + } + + public required init(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder); + } + + internal override func initialize() + { + super.initialize(); + + renderer = PieChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler); + } + + public override func drawRect(rect: CGRect) + { + super.drawRect(rect); + + if (_dataNotSet) + { + return; + } + + let context = UIGraphicsGetCurrentContext(); + + renderer!.drawData(context: context); + + if (self.highlightEnabled && valuesToHighlight()) + { + renderer!.drawHighlighted(context: context, indices: _indicesToHightlight); + } + + renderer!.drawExtras(context: context); + + renderer!.drawValues(context: context); + + _legendRenderer.renderLegend(context: context, legend: _legend); + + drawDescription(context: context); + } + + internal override func calculateOffsets() + { + super.calculateOffsets(); + + // prevent nullpointer when no data set + if (_dataNotSet) + { + return; + } + + var radius = diameter / 2.0; + + var c = centerOffsets; + + // create the circle box that will contain the pie-chart (the bounds of the pie-chart) + _circleBox.origin.x = c.x - radius; + _circleBox.origin.y = c.y - radius; + _circleBox.size.width = radius * 2.0; + _circleBox.size.height = radius * 2.0; + } + + internal override func calcMinMax() + { + super.calcMinMax(); + + calcAngles(); + } + + public override func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + { + /// PieChart does not support MarkerView + return CGPoint(x: 0.0, y: 0.0); + } + + /// calculates the needed angles for the chart slices + private func calcAngles() + { + _drawAngles = [CGFloat](); + _absoluteAngles = [CGFloat](); + + _drawAngles.reserveCapacity(_data.yValCount); + _absoluteAngles.reserveCapacity(_data.yValCount); + + var dataSets = _data.dataSets; + + var cnt = 0; + + for (var i = 0; i < _data.dataSetCount; i++) + { + var set = dataSets[i]; + var entries = set.yVals; + + for (var j = 0; j < entries.count; j++) + { + _drawAngles.append(calcAngle(abs(entries[j].value))); + + if (cnt == 0) + { + _absoluteAngles.append(_drawAngles[cnt]); + } + else + { + _absoluteAngles.append(_absoluteAngles[cnt - 1] + _drawAngles[cnt]); + } + + cnt++; + } + } + } + + /// checks if the given index in the given DataSet is set for highlighting or not + public func needsHighlight(#xIndex: Int, dataSetIndex: Int) -> Bool + { + // no highlight + if (!valuesToHighlight() || dataSetIndex < 0) + { + return false; + } + + for (var i = 0; i < _indicesToHightlight.count; i++) + { + // check if the xvalue for the given dataset needs highlight + if (_indicesToHightlight[i].xIndex == xIndex + && _indicesToHightlight[i].dataSetIndex == dataSetIndex) + { + return true; + } + } + + return false; + } + + /// calculates the needed angle for a given value + private func calcAngle(value: Float) -> CGFloat + { + return CGFloat(value) / CGFloat(_data.yValueSum) * 360.0; + } + + public override func indexForAngle(angle: CGFloat) -> Int + { + // take the current angle of the chart into consideration + var a = (angle - _rotationAngle + 360) % 360.0; + + for (var i = 0; i < _absoluteAngles.count; i++) + { + if (_absoluteAngles[i] > a) + { + return i; + } + } + + return -1; // return -1 if no index found + } + + /// Returns the index of the DataSet this x-index belongs to. + public func dataSetIndexForIndex(xIndex: Int) -> Int + { + var dataSets = _data.dataSets; + + for (var i = 0; i < dataSets.count; i++) + { + if (dataSets[i].entryForXIndex(xIndex) !== nil) + { + return i; + } + } + + return -1; + } + + /// returns an integer array of all the different angles the chart slices + /// have the angles in the returned array determine how much space (of 360°) + /// each slice takes + public var drawAngles: [CGFloat] + { + return _drawAngles; + } + + /// returns the absolute angles of the different chart slices (where the + /// slices end) + public var absoluteAngles: [CGFloat] + { + return _absoluteAngles; + } + + /// Sets the color for the hole that is drawn in the center of the PieChart (if enabled). + /// NOTE: Use holeTransparent with holeColor = nil to make the hole transparent. + public var holeColor: UIColor? + { + get + { + return (renderer as! PieChartRenderer).holeColor!; + } + set + { + (renderer as! PieChartRenderer).holeColor = newValue; + setNeedsDisplay(); + } + } + + /// Set the hole in the center of the PieChart transparent + public var holeTransparent: Bool + { + get + { + return (renderer as! PieChartRenderer).holeTransparent; + } + set + { + (renderer as! PieChartRenderer).holeTransparent = newValue; + setNeedsDisplay(); + } + } + + /// Returns true if the hole in the center of the PieChart is transparent, false if not. + public var isHoleTransparent: Bool + { + return (renderer as! PieChartRenderer).holeTransparent; + } + + /// true if the hole in the center of the pie-chart is set to be visible, false if not + public var drawHoleEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).drawHoleEnabled; + } + set + { + (renderer as! PieChartRenderer).drawHoleEnabled = newValue; + setNeedsDisplay(); + } + } + + /// :returns: true if the hole in the center of the pie-chart is set to be visible, false if not + public var isDrawHoleEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).drawHoleEnabled; + } + } + + /// the text that is displayed in the center of the pie-chart. By default, the text is "Total value + sum of all values" + public var centerText: String! + { + get + { + return (renderer as! PieChartRenderer).centerText; + } + set + { + (renderer as! PieChartRenderer).centerText = newValue; + setNeedsDisplay(); + } + } + + /// true if drawing the center text is enabled + public var drawCenterTextEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).drawCenterTextEnabled; + } + set + { + (renderer as! PieChartRenderer).drawCenterTextEnabled = newValue; + setNeedsDisplay(); + } + } + + /// :returns: true if drawing the center text is enabled + public var isDrawCenterTextEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).drawCenterTextEnabled; + } + } + + internal override var requiredBottomOffset: CGFloat + { + return _legend.font.pointSize * 4.0; + } + + internal override var requiredBaseOffset: CGFloat + { + return 0.0; + } + + public override var radius: CGFloat + { + return _circleBox.width / 2.0; + } + + /// returns the circlebox, the boundingbox of the pie-chart slices + public var circleBox: CGRect + { + return _circleBox; + } + + /// returns the center of the circlebox + public var centerCircleBox: CGPoint + { + return CGPoint(x: _circleBox.midX, y: _circleBox.midY); + } + + /// Sets the font of the center text of the piechart. + public var centerTextFont: UIFont + { + get + { + return (renderer as! PieChartRenderer).centerTextFont; + } + set + { + (renderer as! PieChartRenderer).centerTextFont = newValue; + setNeedsDisplay(); + } + } + + /// Sets the color of the center text of the piechart. + public var centerTextColor: UIColor + { + get + { + return (renderer as! PieChartRenderer).centerTextColor; + } + set + { + (renderer as! PieChartRenderer).centerTextColor = newValue; + setNeedsDisplay(); + } + } + + /// the radius of the hole in the center of the piechart in percent of the maximum radius (max = the radius of the whole chart) + /// :default: 0.5 (50%) (half the pie) + public var holeRadiusPercent: CGFloat + { + get + { + return (renderer as! PieChartRenderer).holeRadiusPercent; + } + set + { + (renderer as! PieChartRenderer).holeRadiusPercent = newValue; + setNeedsDisplay(); + } + } + + /// 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) + /// :default: 0.55 (55%) -> means 5% larger than the center-hole by default + public var transparentCircleRadiusPercent: CGFloat + { + get + { + return (renderer as! PieChartRenderer).transparentCircleRadiusPercent; + } + set + { + (renderer as! PieChartRenderer).transparentCircleRadiusPercent = newValue; + setNeedsDisplay(); + } + } + + /// set this to true to draw the x-value text into the pie slices + public var drawSliceTextEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).drawXLabelsEnabled; + } + set + { + (renderer as! PieChartRenderer).drawXLabelsEnabled = newValue; + setNeedsDisplay(); + } + } + + /// :returns: true if drawing x-values is enabled, false if not + public var isDrawSliceTextEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).drawXLabelsEnabled; + } + } + + /// 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. + public var usePercentValuesEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).usePercentValuesEnabled; + } + set + { + (renderer as! PieChartRenderer).usePercentValuesEnabled = newValue; + setNeedsDisplay(); + } + } + + /// :returns: true if drawing x-values is enabled, false if not + public var isUsePercentValuesEnabled: Bool + { + get + { + return (renderer as! PieChartRenderer).usePercentValuesEnabled; + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/PieRadarChartViewBase.swift b/Charts/Classes/Charts/PieRadarChartViewBase.swift new file mode 100644 index 0000000000..4a92219d3e --- /dev/null +++ b/Charts/Classes/Charts/PieRadarChartViewBase.swift @@ -0,0 +1,504 @@ +// +// PieRadarChartViewBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Base class of PieChartView and RadarChartView. +public class PieRadarChartViewBase: ChartViewBase +{ + /// holds the current rotation angle of the chart + internal var _rotationAngle = CGFloat(270.0) + + /// flag that indicates if rotation is enabled or not + public var rotationEnabled = true + + private var _tapGestureRecognizer: UITapGestureRecognizer! + + public override init(frame: CGRect) + { + super.init(frame: frame); + } + + public required init(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder); + } + + deinit + { + spinAnimationLoop(); + } + + internal override func initialize() + { + super.initialize(); + + _tapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapGestureRecognized:")); + self.addGestureRecognizer(_tapGestureRecognizer); + } + + internal override func calcMinMax() + { + _deltaX = CGFloat(_data.xVals.count - 1); + } + + public override func notifyDataSetChanged() + { + if (_dataNotSet) + { + return; + } + + calcMinMax(); + + _legend = _legendRenderer.computeLegend(_data, legend: _legend); + + calculateOffsets(); + + setNeedsDisplay(); + } + + internal override func calculateOffsets() + { + var legendRight = CGFloat(0.0); + var legendBottom = CGFloat(0.0); + var legendTop = CGFloat(0.0); + + if (_legend != nil && _legend.enabled) + { + if (_legend.position == .RightOfChartCenter) + { + // this is the space between the legend and the chart + var spacing = CGFloat(13.0); + + legendRight = self.fullLegendWidth + spacing; + } + else if (_legend.position == .RightOfChart) + { + + // this is the space between the legend and the chart + var spacing = CGFloat(8.0); + var legendWidth = self.fullLegendWidth + spacing; + var legendHeight = _legend.neededHeight + _legend.textHeightMax; + + var c = self.center; + + var bottomRight = CGPoint(x: self.bounds.width - legendWidth + 15.0, y: legendHeight + 15); + var distLegend = distanceToCenter(x: bottomRight.x, y: bottomRight.y); + + var reference = getPosition(center: c, dist: self.radius, + angle: angleForPoint(x: bottomRight.x, y: bottomRight.y)); + + var distReference = distanceToCenter(x: reference.x, y: reference.y); + var min = CGFloat(5.0); + + if (distLegend < distReference) + { + var diff = distReference - distLegend; + legendRight = min + diff; + } + + if (bottomRight.y >= c.y && self.bounds.height - legendWidth > self.bounds.width) + { + legendRight = legendWidth; + } + } + else if (_legend.position == .BelowChartLeft + || _legend.position == .BelowChartRight + || _legend.position == .BelowChartCenter) + { + legendBottom = self.requiredBottomOffset; + } + + legendRight += self.requiredBaseOffset; + legendTop += self.requiredBaseOffset; + } + + var min = CGFloat(10.0); + + var offsetLeft = max(min, self.requiredBaseOffset); + var offsetTop = max(min, legendTop); + var offsetRight = max(min, legendRight); + var offsetBottom = max(min, max(self.requiredBaseOffset, legendBottom)); + + _viewPortHandler.restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom); + } + + /// returns the angle relative to the chart center for the given point on the chart in degrees. + /// The angle is always between 0 and 360°, 0° is NORTH, 90° is EAST, ... + public func angleForPoint(#x: CGFloat, y: CGFloat) -> CGFloat + { + var c = centerOffsets; + + var tx = Double(x - c.x); + var ty = Double(y - c.y); + var length = sqrt(tx * tx + ty * ty); + var r = acos(ty / length); + + var angle = r * ChartUtils.Math.RAD2DEG; + + if (x > c.x) + { + angle = 360.0 - angle; + } + + // add 90° because chart starts EAST + angle = angle + 90.0; + + // neutralize overflow + if (angle > 360.0) + { + angle = angle - 360.0; + } + + return CGFloat(angle); + } + + /// Calculates the position around a center point, depending on the distance + /// from the center, and the angle of the position around the center. + internal func getPosition(#center: CGPoint, dist: CGFloat, angle: CGFloat) -> CGPoint + { + var a = cos(angle * ChartUtils.Math.FDEG2RAD); + return CGPoint(x: center.x + dist * cos(angle * ChartUtils.Math.FDEG2RAD), + y: center.y + dist * sin(angle * ChartUtils.Math.FDEG2RAD)); + } + + /// Returns the distance of a certain point on the chart to the center of the chart. + public func distanceToCenter(#x: CGFloat, y: CGFloat) -> CGFloat + { + var c = self.centerOffsets; + + var dist = CGFloat(0.0); + + var xDist = CGFloat(0.0); + var yDist = CGFloat(0.0); + + if (x > c.x) + { + xDist = x - c.x; + } + else + { + xDist = c.x - x; + } + + if (y > c.y) + { + yDist = y - c.y; + } + else + { + yDist = c.y - y; + } + + // pythagoras + dist = sqrt(pow(xDist, 2.0) + pow(yDist, 2.0)); + + return dist; + } + + /// Returns the xIndex for the given angle around the center of the chart. + /// Returns -1 if not found / outofbounds. + public func indexForAngle(angle: CGFloat) -> Int + { + fatalError("indexForAngle() cannot be called on PieRadarChartViewBase"); + } + + /// current rotation angle of the pie chart + /// :default: 270f --> top (NORTH) + public var rotationAngle: CGFloat + { + get + { + return _rotationAngle; + } + set + { + while (_rotationAngle < 0.0) + { + _rotationAngle += 360.0; + } + _rotationAngle = newValue % 360.0; + setNeedsDisplay(); + } + } + + /// returns the diameter of the pie- or radar-chart + public var diameter: CGFloat + { + var content = _viewPortHandler.contentRect; + return min(content.width, content.height); + } + + /// Returns the radius of the chart in pixels. + public var radius: CGFloat + { + fatalError("radius cannot be called on PieRadarChartViewBase"); + } + + /// Returns the required bottom offset for the chart. + internal var requiredBottomOffset: CGFloat + { + fatalError("requiredBottomOffset cannot be called on PieRadarChartViewBase"); + } + + /// Returns the base offset needed for the chart without calculating the + /// legend size. + internal var requiredBaseOffset: CGFloat + { + fatalError("requiredBaseOffset cannot be called on PieRadarChartViewBase"); + } + + /// Returns the required right offset for the chart. + private var fullLegendWidth: CGFloat + { + return _legend.textWidthMax + _legend.formSize + _legend.formToTextSpace; + } + + public override var chartXMax: Float + { + return 0.0; + } + + public override var chartXMin: Float + { + return 0.0; + } + + /// Returns an array of SelInfo objects for the given x-index. + /// The SelInfo objects give information about the value at the selected index and the DataSet it belongs to. + public func getYValsAtIndex(xIndex: Int) -> [ChartSelInfo] + { + var vals = [ChartSelInfo](); + + for (var i = 0; i < _data.dataSetCount; i++) + { + var dataSet = _data.getDataSetByIndex(i); + + // extract all y-values from all DataSets at the given x-index + var yVal = dataSet!.yValForXIndex(xIndex); + + if (!isnan(yVal)) + { + vals.append(ChartSelInfo(value: yVal, dataSetIndex: i, dataSet: dataSet!)); + } + } + + return vals; + } + + public var isRotationEnabled: Bool { return rotationEnabled; } + + // MARK: - Animation + + private var _spinDisplayLink: CADisplayLink!; + private var _spinFromAngle: CGFloat = 0.0; + private var _spinToAngle: CGFloat = 0.0; + private var _spinStartTime: NSTimeInterval = 0.0; + private var _spinEndTime: NSTimeInterval = 0.0; + + /// Applys a spin animation to the Chart. + public func spin(duration: NSTimeInterval, fromAngle: CGFloat, toAngle: CGFloat) + { + if (_spinDisplayLink != nil) + { + _spinDisplayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + } + + self.rotationAngle = fromAngle; + + _spinDisplayLink = CADisplayLink(target: self, selector: Selector("spinAnimationLoop")); + _spinFromAngle = fromAngle; + _spinToAngle = toAngle; + _spinStartTime = CACurrentMediaTime(); + _spinEndTime = _spinStartTime + duration; + + _spinDisplayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + } + + public func stopSpinAnimation() + { + if (_spinDisplayLink != nil) + { + _spinDisplayLink.removeFromRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes); + _spinDisplayLink = nil; + } + } + + @objc private func spinAnimationLoop() + { + var currentTime = CACurrentMediaTime(); + var duration = _spinEndTime - _spinStartTime; + var value = duration == 0.0 ? 0.0 : CGFloat((currentTime - _spinStartTime) / duration); + if (value > 1.0) + { + value = 1.0; + } + + if (currentTime >= _spinEndTime) + { + stopSpinAnimation(); + } + + self.rotationAngle = (_spinToAngle - _spinFromAngle) * value + _spinFromAngle; + } + + // MARK: - Gestures + + private var _touchStartPoint: CGPoint!; + private var _isRotating = false; + private var _startAngle = CGFloat(0.0) + + public override func touchesBegan(touches: Set, withEvent event: UIEvent) + { + super.touchesBegan(touches, withEvent: event); + + // if rotation by touch is enabled + if (rotationEnabled) + { + var touch = touches.first as! UITouch!; + + var touchLocation = touch.locationInView(self); + _touchStartPoint = touchLocation; + + self.setStartAngle(x: touchLocation.x, y: touchLocation.y); + } + } + + public override func touchesMoved(touches: Set, withEvent event: UIEvent) + { + super.touchesMoved(touches, withEvent: event); + + if (rotationEnabled) + { + var touch = touches.first as! UITouch!; + + var touchLocation = touch.locationInView(self); + + if (!_isRotating && distance(eventX: touchLocation.x, startX: _touchStartPoint.x, eventY: touchLocation.y, startY: _touchStartPoint.y) > CGFloat(8.0)) + { + _isRotating = true; + self.disableScroll(); + } + else + { + self.updateRotation(x: touchLocation.x, y: touchLocation.y); + } + } + } + + public override func touchesEnded(touches: Set, withEvent event: UIEvent) + { + super.touchesEnded(touches, withEvent: event); + + if (rotationEnabled) + { + var touch = touches.first as! UITouch!; + + var touchLocation = touch.locationInView(self); + _touchStartPoint = touchLocation; + + self.enableScroll(); + _isRotating = false; + } + } + + /// returns the distance between two points + private func distance(#eventX: CGFloat, startX: CGFloat, eventY: CGFloat, startY: CGFloat) -> CGFloat + { + var dx = eventX - startX; + var dy = eventY - startY; + return sqrt(dx * dx + dy * dy); + } + + /// sets the starting angle of the rotation, this is only used by the touch listener, x and y is the touch position + private func setStartAngle(#x: CGFloat, y: CGFloat) + { + _startAngle = angleForPoint(x: x, y: y); + + // take the current angle into consideration when starting a new drag + _startAngle -= _rotationAngle; + + setNeedsDisplay(); + } + + /// updates the view rotation depending on the given touch position, also takes the starting angle into consideration + private func updateRotation(#x: CGFloat, y: CGFloat) + { + self.rotationAngle = angleForPoint(x: x, y: y) - _startAngle; + } + + /// reference to the last highlighted object + private var _lastHighlight: ChartHighlight!; + + @objc private func tapGestureRecognized(recognizer: UITapGestureRecognizer) + { + if (recognizer.state == UIGestureRecognizerState.Ended) + { + var location = recognizer.locationInView(self); + var distance = distanceToCenter(x: location.x, y: location.y); + + // check if a slice was touched + if (distance > self.radius) + { + // if no slice was touched, highlight nothing + self.highlightValues(nil); + _lastHighlight = nil; + _lastHighlight = nil; + } + else + { + var angle = angleForPoint(x: location.x, y: location.y); + + if (self.isKindOfClass(PieChartView)) + { + angle /= _animator.phaseY; + } + + var index = indexForAngle(angle); + + // check if the index could be found + if (index < 0) + { + self.highlightValues(nil); + _lastHighlight = nil; + } + else + { + var valsAtIndex = getYValsAtIndex(index); + + var dataSetIndex = 0; + + // get the dataset that is closest to the selection (PieChart only has one DataSet) + if (self.isKindOfClass(RadarChartView)) + { + dataSetIndex = ChartUtils.closestDataSetIndex(valsAtIndex, value: Float(distance / (self as! RadarChartView).factor), axis: nil); + } + + var h = ChartHighlight(xIndex: index, dataSetIndex: dataSetIndex); + + if (_lastHighlight !== nil && h == _lastHighlight) + { + self.highlightValue(highlight: nil, callDelegate: true); + _lastHighlight = nil; + } + else + { + self.highlightValue(highlight: h, callDelegate: true); + _lastHighlight = h; + } + } + } + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Charts/RadarChartView.swift b/Charts/Classes/Charts/RadarChartView.swift new file mode 100644 index 0000000000..166fedc25d --- /dev/null +++ b/Charts/Classes/Charts/RadarChartView.swift @@ -0,0 +1,240 @@ +// +// RadarChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Implementation of the RadarChart, a "spidernet"-like chart. It works best +/// when displaying 5-10 entries per DataSet. +public class RadarChartView: PieRadarChartViewBase +{ + /// width of the web lines that come from the center. + public var webLineWidth = CGFloat(1.5) + + /// width of the web lines that are in between the lines coming from the center + public var innerWebLineWidth = CGFloat(0.75) + + /// color for the web lines that come from the center + public var webColor = UIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0) + + /// color for the web lines in between the lines that come from the center. + public var innerWebColor = UIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0) + + /// transparency the grid is drawn with (0.0 - 1.0) + public var webAlpha: CGFloat = 150.0 / 255.0 + + /// flag indicating if the web lines should be drawn or not + public var drawWeb = true + + /// the object reprsenting the y-axis labels + private var _yAxis: ChartYAxis! + + /// the object representing the x-axis labels + private var _xAxis: ChartXAxis! + + internal var _yAxisRenderer: ChartYAxisRendererRadarChart! + internal var _xAxisRenderer: ChartXAxisRendererRadarChart! + + public override init(frame: CGRect) + { + super.init(frame: frame); + } + + public required init(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder); + } + + internal override func initialize() + { + super.initialize(); + + _yAxis = ChartYAxis(position: .Left); + _xAxis = ChartXAxis(); + _xAxis.spaceBetweenLabels = 0; + + renderer = RadarChartRenderer(chart: self, animator: _animator, viewPortHandler: _viewPortHandler); + + _yAxisRenderer = ChartYAxisRendererRadarChart(viewPortHandler: _viewPortHandler, yAxis: _yAxis, chart: self); + _xAxisRenderer = ChartXAxisRendererRadarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, chart: self); + } + + internal override func calcMinMax() + { + super.calcMinMax(); + + var minLeft = _data.getYMin(.Left); + var maxLeft = _data.getYMax(.Left); + + _chartXMax = Float(_data.xVals.count) - 1.0; + _deltaX = CGFloat(abs(_chartXMax - _chartXMin)); + + var leftRange = CGFloat(abs(maxLeft - (_yAxis.isStartAtZeroEnabled ? 0.0 : minLeft))); + + var topSpaceLeft = leftRange * _yAxis.spaceTop; + var bottomSpaceLeft = leftRange * _yAxis.spaceBottom; + + _chartXMax = Float(_data.xVals.count) - 1.0; + _deltaX = CGFloat(abs(_chartXMax - _chartXMin)); + + _yAxis.axisMaximum = !isnan(_yAxis.customAxisMax) ? _yAxis.customAxisMax : maxLeft + Float(topSpaceLeft); + _yAxis.axisMinimum = !isnan(_yAxis.customAxisMin) ? _yAxis.customAxisMin : minLeft - Float(bottomSpaceLeft); + + // consider starting at zero (0) + if (_yAxis.isStartAtZeroEnabled) + { + _yAxis.axisMinimum = 0.0; + } + + _yAxis.axisRange = abs(_yAxis.axisMaximum - _yAxis.axisMinimum); + } + + public override func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + { + var angle = self.sliceAngle * CGFloat(entry.xIndex) + self.rotationAngle; + var val = CGFloat(entry.value) * self.factor; + var c = self.centerOffsets; + + var p = CGPoint(x: c.x + val * cos(angle * ChartUtils.Math.FDEG2RAD), + y: c.y + val * sin(angle * ChartUtils.Math.FDEG2RAD)); + + return p; + } + + public override func notifyDataSetChanged() + { + if (_dataNotSet) + { + return; + } + + calcMinMax(); + + _yAxis?._defaultValueFormatter = _defaultValueFormatter; + + _yAxisRenderer?.computeAxis(yMin: _yAxis.axisMinimum, yMax: _yAxis.axisMaximum); + _xAxisRenderer?.computeAxis(xValAverageLength: _data.xValAverageLength, xValues: _data.xVals); + + _legend = _legendRenderer?.computeLegend(_data, legend: _legend); + + calculateOffsets(); + + setNeedsDisplay(); + } + + public override func drawRect(rect: CGRect) + { + super.drawRect(rect); + + if (_dataNotSet) + { + return; + } + + let context = UIGraphicsGetCurrentContext(); + + _xAxisRenderer?.renderAxisLabels(context: context); + + if (drawWeb) + { + renderer!.drawExtras(context: context); + } + + _yAxisRenderer.renderLimitLines(context: context); + + renderer!.drawData(context: context); + + if (self.highlightEnabled && valuesToHighlight()) + { + renderer!.drawHighlighted(context: context, indices: _indicesToHightlight); + } + + _yAxisRenderer.renderAxisLabels(context: context); + + renderer!.drawValues(context: context); + + _legendRenderer.renderLegend(context: context, legend: _legend); + + drawDescription(context: context); + + drawMarkers(context: context); + } + + /// Returns the factor that is needed to transform values into pixels. + public var factor: CGFloat + { + var content = _viewPortHandler.contentRect; + return min(content.width / 2.0, content.height / 2.0) + / CGFloat(_yAxis.axisRange); + } + + /// Returns the angle that each slice in the radar chart occupies. + public var sliceAngle: CGFloat + { + return 360.0 / CGFloat(_data.xValCount); + } + + public override func indexForAngle(angle: CGFloat) -> Int + { + // take the current angle of the chart into consideration + var a = (angle - _rotationAngle + 360.0) % 360.0; + + var sliceAngle = self.sliceAngle; + + for (var i = 0; i < _data.xValCount; i++) + { + if (sliceAngle * CGFloat(i + 1) - sliceAngle / 2.0 > a) + { + return i; + } + } + + return 0; + } + + /// Returns the object that represents all y-labels of the RadarChart. + public var yAxis: ChartYAxis + { + return _yAxis; + } + + /// Returns the object that represents all x-labels that are placed around the RadarChart. + public var xAxis: ChartXAxis + { + return _xAxis; + } + + internal override var requiredBottomOffset: CGFloat + { + return _legend.font.pointSize * 6.5; + } + + internal override var requiredBaseOffset: CGFloat + { + return _xAxis.labelWidth; + } + + public override var radius: CGFloat + { + var content = _viewPortHandler.contentRect; + return min(content.width / 2.0, content.height / 2.0); + } + + /// Returns the maximum value this chart can display on it's y-axis. + public override var chartYMax: Float { return _yAxis.axisMaximum; } + + /// Returns the minimum value this chart can display on it's y-axis. + public override var chartYMin: Float { return _yAxis.axisMinimum; } + + /// Returns the range of y-values this chart can display. + public var yRange: Float { return _yAxis.axisRange} +} \ No newline at end of file diff --git a/Charts/Classes/Charts/ScatterChartView.swift b/Charts/Classes/Charts/ScatterChartView.swift new file mode 100644 index 0000000000..201ae17bca --- /dev/null +++ b/Charts/Classes/Charts/ScatterChartView.swift @@ -0,0 +1,81 @@ +// +// ScatterChartView.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// The ScatterChart. Draws dots, triangles, squares and custom shapes into the chartview. +public class ScatterChartView: BarLineChartViewBase, ScatterChartRendererDelegate +{ + public override func initialize() + { + super.initialize(); + + renderer = ScatterChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler); + _chartXMin = -0.5; + } + + public override func calcMinMax() + { + super.calcMinMax(); + + if (_deltaX == 0.0 && _data.yValCount > 0) + { + _deltaX = 1.0; + } + + _chartXMax += 0.5; + _deltaX = CGFloat(abs(_chartXMax - _chartXMin)); + } + + // MARK: - ScatterChartRendererDelegate + + public func scatterChartRendererData(renderer: ScatterChartRenderer) -> ScatterChartData! + { + return _data as! ScatterChartData!; + } + + public func scatterChartRenderer(renderer: ScatterChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return getTransformer(which); + } + + public func scatterChartDefaultRendererValueFormatter(renderer: ScatterChartRenderer) -> NSNumberFormatter! + { + return self._defaultValueFormatter; + } + + public func scatterChartRendererChartYMax(renderer: ScatterChartRenderer) -> Float + { + return self.chartYMax; + } + + public func scatterChartRendererChartYMin(renderer: ScatterChartRenderer) -> Float + { + return self.chartYMin; + } + + public func scatterChartRendererChartXMax(renderer: ScatterChartRenderer) -> Float + { + return self.chartXMax; + } + + public func scatterChartRendererChartXMin(renderer: ScatterChartRenderer) -> Float + { + return self.chartXMin; + } + + public func scatterChartRendererMaxVisibleValueCount(renderer: ScatterChartRenderer) -> Int + { + return self.maxVisibleValueCount; + } +} \ No newline at end of file diff --git a/Charts/Classes/Components/ChartAxisBase.swift b/Charts/Classes/Components/ChartAxisBase.swift new file mode 100644 index 0000000000..8de42d0184 --- /dev/null +++ b/Charts/Classes/Components/ChartAxisBase.swift @@ -0,0 +1,57 @@ +// +// ChartAxisBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartAxisBase: ChartComponentBase +{ + public var labelFont = UIFont.systemFontOfSize(10.0) + public var labelTextColor = UIColor.blackColor() + + public var axisLineColor = UIColor.grayColor() + public var axisLineWidth = CGFloat(0.5) + public var axisLineDashPhase = CGFloat(0.0) + public var axisLineDashLengths: [CGFloat]! + + public var gridColor = UIColor.grayColor().colorWithAlphaComponent(0.9) + public var gridLineWidth = CGFloat(0.5) + public var gridLineDashPhase = CGFloat(0.0) + public var gridLineDashLengths: [CGFloat]! + + public var drawGridLinesEnabled = true + public var drawAxisLineEnabled = true + + /// flag that indicates of the labels of this axis should be drawn or not + public var drawLabelsEnabled = true + + public var xOffset = CGFloat(5.0) + public var yOffset = CGFloat(5.0) + + public override init() + { + super.init(); + } + + public func getLongestLabel() -> String + { + fatalError("getLongestLabel() cannot be called on ChartAxisBase"); + } + + public var isDrawGridLinesEnabled: Bool { return drawGridLinesEnabled; } + + public var isDrawAxisLineEnabled: Bool { return drawAxisLineEnabled; } + + public var isDrawLabelsEnabled: Bool { return drawLabelsEnabled; } +} \ No newline at end of file diff --git a/Charts/Classes/Components/ChartComponentBase.swift b/Charts/Classes/Components/ChartComponentBase.swift new file mode 100644 index 0000000000..32996ee3b5 --- /dev/null +++ b/Charts/Classes/Components/ChartComponentBase.swift @@ -0,0 +1,29 @@ +// +// ChartComponentBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 16/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +/// This class encapsulates everything both Axis and Legend have in common. +public class ChartComponentBase: NSObject +{ + /// flag that indicates if this component is enabled or not + public var enabled = true + + public override init() + { + super.init(); + } + + public var isEnabled: Bool { return enabled; } +} diff --git a/Charts/Classes/Components/ChartLegend.swift b/Charts/Classes/Components/ChartLegend.swift new file mode 100644 index 0000000000..97ffec4d66 --- /dev/null +++ b/Charts/Classes/Components/ChartLegend.swift @@ -0,0 +1,223 @@ +// +// ChartLegend.swift +// Charts +// +// Created by Daniel Cohen Gindi on 24/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartLegend: ChartComponentBase +{ + @objc + public enum ChartLegendPosition: Int + { + case RightOfChart + case RightOfChartCenter + case RightOfChartInside + case BelowChartLeft + case BelowChartRight + case BelowChartCenter + case PiechartCenter + } + + @objc + public enum ChartLegendForm: Int + { + case Square + case Circle + case Line + } + + private var _colors = [UIColor?]() + private var _labels = [String?]() + + public var position = ChartLegendPosition.BelowChartLeft + + public var font: UIFont = UIFont.systemFontOfSize(10.0) + public var textColor = UIColor.blackColor() + + public var form = ChartLegendForm.Square + public var formSize = CGFloat(8.0) + public var formLineWidth = CGFloat(1.5) + + public var xEntrySpace = CGFloat(6.0) + public var yEntrySpace = CGFloat(5.0) + public var formToTextSpace = CGFloat(5.0) + public var stackSpace = CGFloat(3.0) + + public var xOffset = CGFloat(5.0) + public var yOffset = CGFloat(6.0) + + public override init() + { + super.init(); + } + + public init(colors: [UIColor?], labels: [String?]) + { + super.init(); + self._colors = colors; + self._labels = labels; + + validateLabelsAndColors(); + } + + public func getMaximumEntrySize(font: UIFont) -> CGSize + { + var maxW = CGFloat(0.0); + var maxH = CGFloat(0.0); + + for (var i = 0; i < _labels.count; i++) + { + if (_labels[i] == nil) + { + continue; + } + + var size = (_labels[i] as NSString!).sizeWithAttributes([NSFontAttributeName: font]); + + if (size.width > maxW) + { + maxW = size.width; + } + if (size.height > maxH) + { + maxH = size.height; + } + } + + return CGSize( + width: maxW + formSize + formToTextSpace, + height: maxH + ); + } + + public var colors: [UIColor?] + { + get + { + return _colors; + } + } + + public var labels: [String?] + { + get + { + return _labels; + } + set + { + _labels = newValue; + validateLabelsAndColors(); + } + } + + private func validateLabelsAndColors() + { + if (_labels.count != _colors.count) + { + println("colors array and labels array need to be of same size"); + + while (colors.count > labels.count) + { + self._colors.removeLast(); + } + while (labels.count > colors.count) + { + self._labels.removeLast(); + } + } + } + + public func getLabel(index: Int) -> String? + { + return _labels[index]; + } + + public func apply(legend: ChartLegend) + { + position = legend.position; + font = legend.font; + textColor = legend.textColor; + form = legend.form; + formSize = legend.formSize; + formLineWidth = legend.formLineWidth; + xEntrySpace = legend.xEntrySpace; + yEntrySpace = legend.yEntrySpace; + formToTextSpace = legend.formToTextSpace; + stackSpace = legend.stackSpace; + enabled = legend.enabled; + xOffset = legend.xOffset; + yOffset = legend.yOffset; + } + + public func getFullSize(labelFont: UIFont) -> CGSize + { + var width = CGFloat(0.0); + var height = CGFloat(0.0); + + for (var i = 0; i < _labels.count; i++) + { + if (labels[i] != nil) + { + // make a step to the left + if (_colors[i] != nil) + { + width += formSize + formToTextSpace; + } + + var size = (_labels[i] as NSString!).sizeWithAttributes([NSFontAttributeName: labelFont]); + + width += size.width; + width += xEntrySpace; + + height += size.height; + height += yEntrySpace; + } + else + { + width += formSize + stackSpace; + } + } + + return CGSize(width: width, height: height); + } + + public var neededWidth = CGFloat(0.0); + public var neededHeight = CGFloat(0.0); + public var textWidthMax = CGFloat(0.0); + public var textHeightMax = CGFloat(0.0); + + public func calculateDimensions(labelFont: UIFont) + { + var maxEntrySize = getMaximumEntrySize(labelFont); + var fullSize = getFullSize(labelFont); + + if (position == .RightOfChart + || position == .RightOfChartCenter + || position == .PiechartCenter) + { + neededWidth = maxEntrySize.width; + neededHeight = fullSize.height; + textWidthMax = maxEntrySize.width; + textHeightMax = maxEntrySize.height; + } + else + { + neededWidth = fullSize.width; + neededHeight = maxEntrySize.height; + textWidthMax = maxEntrySize.width; + textHeightMax = maxEntrySize.height; + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Components/ChartLimitLine.swift b/Charts/Classes/Components/ChartLimitLine.swift new file mode 100644 index 0000000000..1309f062ee --- /dev/null +++ b/Charts/Classes/Components/ChartLimitLine.swift @@ -0,0 +1,76 @@ +// +// ChartLimitLine.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartLimitLine: ChartComponentBase +{ + @objc + public enum LabelPosition: Int + { + case Left + case Right + } + + public var limit = Float(0.0) + private var _lineWidth = CGFloat(2.0) + public var lineColor = UIColor(red: 237.0/255.0, green: 91.0/255.0, blue: 91.0/255.0, alpha: 1.0) + public var lineDashPhase = CGFloat(0.0) + public var lineDashLengths: [CGFloat]? + public var valueTextColor = UIColor.blackColor() + public var valueFont = UIFont.systemFontOfSize(13.0) + public var label = "" + public var labelPosition = LabelPosition.Right + + public override init() + { + super.init(); + } + + public init(limit: Float) + { + super.init(); + self.limit = limit; + } + + public init(limit: Float, label: String) + { + super.init(); + self.limit = limit; + self.label = label; + } + + /// set the line width of the chart (min = 0.2f, max = 12f); default 2f + public var lineWidth: CGFloat + { + get + { + return _lineWidth; + } + set + { + _lineWidth = newValue; + + if (_lineWidth < 0.2) + { + _lineWidth = 0.2; + } + if (_lineWidth > 12.0) + { + _lineWidth = 12.0; + } + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Components/ChartMarker.swift b/Charts/Classes/Components/ChartMarker.swift new file mode 100644 index 0000000000..8e38f946ac --- /dev/null +++ b/Charts/Classes/Components/ChartMarker.swift @@ -0,0 +1,57 @@ +// +// ChartMarker.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit; + +public class ChartMarker: ChartComponentBase +{ + /// The marker image to render + public var image: UIImage? + + /// Use this to return the desired offset you wish the MarkerView to have on the x-axis. + public var offset: CGPoint = CGPoint() + + /// The marker's size + public var size: CGSize + { + get + { + return image!.size; + } + } + + public override init() + { + super.init(); + } + + /// Draws the ChartMarker on the given position on the given context + public func draw(#context: CGContext, point: CGPoint) + { + var offset = self.offset; + var size = self.size; + + var rect = CGRect(x: point.x + offset.x, y: point.y + offset.y, width: size.width, height: size.height); + + UIGraphicsPushContext(context); + image!.drawInRect(rect); + UIGraphicsPopContext(); + } + + /// This method enables a custom ChartMarker to update it's content everytime the MarkerView is redrawn according to the data entry it points to. + public func refreshContent(#entry: ChartDataEntry, dataSetIndex: Int) + { + // Do nothing here... + } +} \ No newline at end of file diff --git a/Charts/Classes/Components/ChartXAxis.swift b/Charts/Classes/Components/ChartXAxis.swift new file mode 100644 index 0000000000..ac20743f64 --- /dev/null +++ b/Charts/Classes/Components/ChartXAxis.swift @@ -0,0 +1,83 @@ +// +// ChartXAxis.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartXAxis: ChartAxisBase +{ + @objc + public enum XAxisLabelPosition: Int + { + case Top + case Bottom + case BothSided + case TopInside + case BottomInside + } + + public var values = [String]() + public var labelWidth = CGFloat(1.0) + public var labelHeight = CGFloat(1.0) + + /// the space that should be left out (in characters) between the x-axis labels + public var spaceBetweenLabels = Int(4) + + /// the modulus that indicates if a value at a specified index in an array(list) for the x-axis-labels is drawn or not. Draw when (index % modulus) == 0. + public var axisLabelModulus = Int(1) + + /// the modulus that indicates if a value at a specified index in an array(list) for the y-axis-labels is drawn or not. Draw when (index % modulus) == 0. + /// Used only for Horizontal BarChart + public var yAxisLabelModulus = Int(1) + + /// 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 + public var avoidFirstLastClippingEnabled = false + + /// if set to true, the x-axis label entries will adjust themselves when scaling the graph + public var adjustXLabelsEnabled = true + + /// the position of the x-labels relative to the chart + public var labelPosition = XAxisLabelPosition.Top; + + public override init() + { + super.init(); + } + + public override func getLongestLabel() -> String + { + var longest = ""; + + for (var i = 0; i < values.count; i++) + { + var text = values[i]; + + if (longest.lengthOfBytesUsingEncoding(NSUTF16StringEncoding) < text.lengthOfBytesUsingEncoding(NSUTF16StringEncoding)) + { + longest = text; + } + } + + return longest; + } + + public var isAvoidFirstLastClippingEnabled: Bool + { + return avoidFirstLastClippingEnabled; + } + + public var isAdjustXLabelsEnabled: Bool + { + return adjustXLabelsEnabled; + } +} \ No newline at end of file diff --git a/Charts/Classes/Components/ChartYAxis.swift b/Charts/Classes/Components/ChartYAxis.swift new file mode 100644 index 0000000000..da8cc504dc --- /dev/null +++ b/Charts/Classes/Components/ChartYAxis.swift @@ -0,0 +1,246 @@ +// +// ChartYAxis.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +/// Class representing the y-axis labels settings and its entries. +/// Be aware that not all features the YLabels class provides are suitable for the RadarChart. +/// Customizations that affect the value range of the axis need to be applied before setting data for the chart. +public class ChartYAxis: ChartAxisBase +{ + @objc + public enum YAxisLabelPosition: Int + { + case OutsideChart + case InsideChart + } + + /// Enum that specifies the axis a DataSet should be plotted against, either Left or Right. + @objc + public enum AxisDependency: Int + { + case Left + case Right + } + + public var entries = [Float]() + public var entryCount: Int { return entries.count; } + + /// the number of y-label entries the y-labels should have, default 6 + private var _labelCount = Int(6) + + /// indicates if the top y-label entry is drawn or not + public var drawTopYLabelEntryEnabled = true + + /// if true, the y-labels show only the minimum and maximum value + public var showOnlyMinMaxEnabled = false + + /// flag that indicates if the axis is inverted or not + public var inverted = false + + /// if true, the y-label entries will always start at zero + public var startAtZeroEnabled = true + + /// the formatter used to customly format the y-labels + public var valueFormatter: NSNumberFormatter? + + /// the formatter used to customly format the y-labels + internal var _defaultValueFormatter = NSNumberFormatter() + + /// array of limitlines that can be set for the axis + private var _limitLines = [ChartLimitLine]() + + /// A custom minimum value for this axis. + /// If set, this value will not be calculated automatically depending on the provided data. + /// Use resetcustomAxisMin() to undo this. + /// Do not forget to set startAtZeroEnabled = false if you use this method. + /// Otherwise, the axis-minimum value will still be forced to 0. + public var customAxisMin = Float.NaN + + /// Set a custom maximum value for this axis. + /// If set, this value will not be calculated automatically depending on the provided data. + /// Use resetcustomAxisMax() to undo this. + public var customAxisMax = Float.NaN + + /// axis space from the largest value to the top in percent of the total axis range + public var spaceTop = CGFloat(0.1) + + /// axis space from the smallest value to the bottom in percent of the total axis range + public var spaceBottom = CGFloat(0.1) + + public var axisMaximum = Float(0) + public var axisMinimum = Float(0) + + /// the total range of values this axis covers + public var axisRange = Float(0) + + /// the position of the y-labels relative to the chart + public var labelPosition = YAxisLabelPosition.OutsideChart + + /// the side this axis object represents + private var _axisDependency = AxisDependency.Left + + public override init() + { + super.init(); + + _defaultValueFormatter.maximumFractionDigits = 1; + _defaultValueFormatter.minimumFractionDigits = 1; + _defaultValueFormatter.usesGroupingSeparator = true; + } + + public init(position: AxisDependency) + { + super.init(); + + _axisDependency = position; + + _defaultValueFormatter.maximumFractionDigits = 1; + _defaultValueFormatter.minimumFractionDigits = 1; + _defaultValueFormatter.usesGroupingSeparator = true; + } + + public var axisDependency: AxisDependency + { + return _axisDependency; + } + + /// the number of label entries the y-axis should have + /// max = 15, + /// min = 2, + /// default = 6, + /// be aware that this number is not fixed and can only be approximated + public var labelCount: Int + { + get + { + return _labelCount; + } + set + { + _labelCount = newValue; + + if (_labelCount > 15) + { + _labelCount = 15; + } + if (_labelCount < 2) + { + _labelCount = 2; + } + } + } + + /// Adds a new ChartLimitLine to this axis. + public func addLimitLine(line: ChartLimitLine) + { + _limitLines.append(line); + } + + /// Removes the specified ChartLimitLine from the axis. + public func removeLimitLine(line: ChartLimitLine) + { + for (var i = 0; i < _limitLines.count; i++) + { + if (_limitLines[i] === line) + { + _limitLines.removeAtIndex(i); + return; + } + } + } + + /// Removes all LimitLines from the axis. + public func removeAllLimitLines() + { + _limitLines.removeAll(keepCapacity: false); + } + + /// Returns the LimitLines of this axis. + public var limitLines : [ChartLimitLine] + { + return _limitLines; + } + + /// By calling this method, any custom minimum value that has been previously set is reseted, and the calculation is done automatically. + public func resetcustomAxisMin() + { + customAxisMin = Float.NaN; + } + + /// By calling this method, any custom maximum value that has been previously set is reseted, and the calculation is done automatically. + public func resetcustomAxisMax() + { + customAxisMax = Float.NaN; + } + + public func requiredSize() -> CGSize + { + var label = getLongestLabel() as NSString; + var size = label.sizeWithAttributes([NSFontAttributeName: labelFont]); + size.width += xOffset * 2.0; + size.height += yOffset * 2.0; + return size; + } + + public override func getLongestLabel() -> String + { + var longest = ""; + + for (var i = 0; i < entries.count; i++) + { + var text = getFormattedLabel(i); + + if (longest.lengthOfBytesUsingEncoding(NSUTF16StringEncoding) < text.lengthOfBytesUsingEncoding(NSUTF16StringEncoding)) + { + longest = text; + } + } + + return longest; + } + + /// Returns the formatted y-label at the specified index. This will either use the auto-formatter or the custom formatter (if one is set). + public func getFormattedLabel(index: Int) -> String + { + if (index < 0 || index >= entries.count) + { + return ""; + } + + return (valueFormatter ?? _defaultValueFormatter).stringFromNumber(entries[index])!; + } + + /// Returns true if this axis needs horizontal offset, false if no offset is needed. + public var needsOffset: Bool + { + if (isEnabled && isDrawLabelsEnabled && labelPosition == .OutsideChart) + { + return true; + } + else + { + return false; + } + } + + public var isInverted: Bool { return inverted; } + + public var isStartAtZeroEnabled: Bool { return startAtZeroEnabled; } + + public var isShowOnlyMinMaxEnabled: Bool { return showOnlyMinMaxEnabled; } + + public var isDrawTopYLabelEntryEnabled: Bool { return drawTopYLabelEntryEnabled; } +} \ No newline at end of file diff --git a/Charts/Classes/Data/BarChartData.swift b/Charts/Classes/Data/BarChartData.swift new file mode 100644 index 0000000000..f5c854eb6e --- /dev/null +++ b/Charts/Classes/Data/BarChartData.swift @@ -0,0 +1,42 @@ +// +// BarChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class BarChartData: BarLineScatterCandleChartData +{ + private var _groupSpace = CGFloat(0.8) + + /// The spacing is relative to a full bar width + public var groupSpace: CGFloat + { + get + { + if (_dataSets.count <= 1) + { + return 0.0; + } + return _groupSpace; + } + set + { + _groupSpace = newValue; + } + } + + /// Returns true if this BarData object contains grouped DataSets (more than 1 DataSet). + public var isGrouped: Bool + { + return _dataSets.count > 1 ? true : false; + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/BarChartDataEntry.swift b/Charts/Classes/Data/BarChartDataEntry.swift new file mode 100644 index 0000000000..c0bc6ecb83 --- /dev/null +++ b/Charts/Classes/Data/BarChartDataEntry.swift @@ -0,0 +1,109 @@ +// +// BarChartDataEntry.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class BarChartDataEntry: ChartDataEntry +{ + /// the values the stacked barchart holds + public var values: [Float]! + + /// Constructor for stacked bar entries. + public init(values: [Float], xIndex: Int) + { + super.init(value: BarChartDataEntry.calcSum(values), xIndex: xIndex); + self.values = values; + } + + /// Constructor for normal bars (not stacked). + public override init(value: Float, xIndex: Int) + { + super.init(value: value, xIndex: xIndex); + } + + /// Constructor for stacked bar entries. + public init(values: [Float], xIndex: Int, label: String) + { + super.init(value: BarChartDataEntry.calcSum(values), xIndex: xIndex, data: label); + self.values = values; + } + + /// Constructor for normal bars (not stacked). + public override init(value: Float, xIndex: Int, data: AnyObject?) + { + super.init(value: value, xIndex: xIndex, data: data) + } + + /// Returns the closest value inside the values array (for stacked barchart) + /// to the value given as a parameter. The closest value must be higher + /// (above) the provided value. + public func getClosestIndexAbove(value: Float) -> Int + { + if (values == nil) + { + return 0; + } + + var index = values.count - 1; + var remainder: Float = 0.0; + + while (index > 0 && value > values[index] + remainder) + { + remainder += values[index]; + index--; + } + + return index; + } + + public func getBelowSum(stackIndex :Int) -> Float + { + if (values == nil) + { + return 0; + } + + var remainder: Float = 0.0; + var index = values.count - 1; + + while (index > stackIndex && index >= 0) + { + remainder += values[index]; + index--; + } + + return remainder; + } + + /// Calculates the sum across all values. + private class func calcSum(values: [Float]) -> Float + { + var sum = Float(0.0); + + for f in values + { + sum += f; + } + + return sum; + } + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! BarChartDataEntry; + copy.values = values; + return copy; + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/BarChartDataSet.swift b/Charts/Classes/Data/BarChartDataSet.swift new file mode 100644 index 0000000000..002413e9dd --- /dev/null +++ b/Charts/Classes/Data/BarChartDataSet.swift @@ -0,0 +1,113 @@ +// +// BarChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class BarChartDataSet: BarLineScatterCandleChartDataSet +{ + /// space indicator between the bars in percentage of the whole width of one value (0.15 == 15% of bar width) + public var barSpace: CGFloat = 0.15 + + /// the maximum number of bars that are stacked upon each other, this value + /// is calculated from the Entries that are added to the DataSet + private var _stackSize = 1 + + /// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value + public var barShadowColor = UIColor(red: 215.0/255.0, green: 215.0/255.0, blue: 215.0/255.0, alpha: 1.0) + + /// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque) + public var highLightAlpha = CGFloat(120.0 / 255.0) + + /// the overall entry count, including counting each stack-value individually + private var _entryCountStacks = 0 + + /// array of labels used to describe the different values of the stacked bars + public var stackLabels: [String] = ["Stack"] + + public override init(yVals: [ChartDataEntry]?, label: String) + { + super.init(yVals: yVals, label: label); + + self.highlightColor = UIColor.blackColor(); + + self.calcStackSize(yVals as! [BarChartDataEntry]?); + self.calcEntryCountIncludingStacks(yVals as! [BarChartDataEntry]?); + } + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! BarChartDataSet; + copy.barSpace = barSpace; + copy._stackSize = _stackSize; + copy.barShadowColor = barShadowColor; + copy.highLightAlpha = highLightAlpha; + copy._entryCountStacks = _entryCountStacks; + copy.stackLabels = stackLabels; + return copy; + } + + /// Calculates the total number of entries this DataSet represents, including + /// stacks. All values belonging to a stack are calculated separately. + private func calcEntryCountIncludingStacks(yVals: [BarChartDataEntry]!) + { + _entryCountStacks = 0; + + for (var i = 0; i < yVals.count; i++) + { + var vals = yVals[i].values; + + if (vals == nil) + { + _entryCountStacks++; + } + else + { + _entryCountStacks += vals.count; + } + } + } + + /// calculates the maximum stacksize that occurs in the Entries array of this DataSet + private func calcStackSize(yVals: [BarChartDataEntry]!) + { + for (var i = 0; i < yVals.count; i++) + { + var vals = yVals[i].values; + + if (vals != nil && vals.count > _stackSize) + { + _stackSize = vals.count; + } + } + } + + /// Returns the maximum number of bars that can be stacked upon another in this DataSet. + public var stackSize: Int + { + return _stackSize; + } + + /// Returns true if this DataSet is stacked (stacksize > 1) or not. + public var isStacked: Bool + { + return _stackSize > 1 ? true : false; + } + + /// returns the overall entry count, including counting each stack-value individually + public var entryCountStacks: Int + { + return _entryCountStacks; + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/BarLineScatterCandleChartData.swift b/Charts/Classes/Data/BarLineScatterCandleChartData.swift new file mode 100644 index 0000000000..d5ea9f0dbf --- /dev/null +++ b/Charts/Classes/Data/BarLineScatterCandleChartData.swift @@ -0,0 +1,32 @@ +// +// BarLineScatterCandleChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class BarLineScatterCandleChartData: ChartData +{ + public override init() + { + super.init(); + } + + public override init(xVals: [String]?) + { + super.init(xVals: xVals); + } + + public override init(xVals: [String]?, dataSets: [ChartDataSet]?) + { + super.init(xVals: xVals, dataSets: dataSets); + } +} diff --git a/Charts/Classes/Data/BarLineScatterCandleChartDataSet.swift b/Charts/Classes/Data/BarLineScatterCandleChartDataSet.swift new file mode 100644 index 0000000000..d60f245052 --- /dev/null +++ b/Charts/Classes/Data/BarLineScatterCandleChartDataSet.swift @@ -0,0 +1,35 @@ +// +// BarLineScatterCandleChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit; + +public class BarLineScatterCandleChartDataSet: ChartDataSet +{ + public var highlightColor = UIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0) + public var highlightLineWidth = CGFloat(1.0) + public var highlightLineDashPhase = CGFloat(0.0) + public var highlightLineDashLengths: [CGFloat]? + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! BarLineScatterCandleChartDataSet; + copy.highlightColor = highlightColor; + copy.highlightLineWidth = highlightLineWidth; + copy.highlightLineDashPhase = highlightLineDashPhase; + copy.highlightLineDashLengths = highlightLineDashLengths; + return copy; + } +} diff --git a/Charts/Classes/Data/CandleChartData.swift b/Charts/Classes/Data/CandleChartData.swift new file mode 100644 index 0000000000..836e835b33 --- /dev/null +++ b/Charts/Classes/Data/CandleChartData.swift @@ -0,0 +1,19 @@ +// +// CandleChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class CandleChartData: BarLineScatterCandleChartData +{ + +} diff --git a/Charts/Classes/Data/CandleChartDataEntry.swift b/Charts/Classes/Data/CandleChartDataEntry.swift new file mode 100644 index 0000000000..49b45ee548 --- /dev/null +++ b/Charts/Classes/Data/CandleChartDataEntry.swift @@ -0,0 +1,86 @@ +// +// CandleChartDataEntry.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class CandleChartDataEntry: ChartDataEntry +{ + /// shadow-high value + public var high = Float(0.0) + + /// shadow-low value + public var low = Float(0.0) + + /// close value + public var close = Float(0.0) + + /// open value + public var open = Float(0.0) + + public init(xIndex: Int, shadowH: Float, shadowL: Float, open: Float, close: Float) + { + super.init(value: (shadowH + shadowL) / 2.0, xIndex: xIndex); + + self.high = shadowH; + self.low = shadowL; + self.open = open; + self.close = close; + } + + public init(xIndex: Int, shadowH: Float, shadowL: Float, open: Float, close: Float, data: AnyObject?) + { + super.init(value: (shadowH + shadowL) / 2.0, xIndex: xIndex, data: data); + + self.high = shadowH; + self.low = shadowL; + self.open = open; + self.close = close; + } + + /// Returns the overall range (difference) between shadow-high and shadow-low. + public var shadowRange: Float + { + return abs(high - low); + } + + /// Returns the body size (difference between open and close). + public var bodyRange: Float + { + return abs(open - close); + } + + /// the center value of the candle. (Middle value between high and low) + public override var value: Float + { + get + { + return super.value; + } + set + { + super.value = (high + low) / 2.0; + } + } + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! CandleChartDataEntry; + copy.high = high; + copy.high = low; + copy.high = open; + copy.high = close; + return copy; + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/CandleChartDataSet.swift b/Charts/Classes/Data/CandleChartDataSet.swift new file mode 100644 index 0000000000..19f77fdfd5 --- /dev/null +++ b/Charts/Classes/Data/CandleChartDataSet.swift @@ -0,0 +1,81 @@ +// +// CandleChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class CandleChartDataSet: BarLineScatterCandleChartDataSet +{ + /// the width of the candle-shadow-line in pixels. + /// :default: 3.0 + public var shadowWidth = CGFloat(1.5) + + /// the space between the candle entries + /// :default: 0.1 (10%) + private var _bodySpace = CGFloat(0.1) + + public override init(yVals: [ChartDataEntry]?, label: String) + { + super.init(yVals: yVals, label: label); + } + + internal override func calcMinMax() + { + if (yVals.count == 0) + { + return; + } + + var entries = yVals as! [CandleChartDataEntry]; + + _yMin = entries[0].low; + _yMax = entries[0].high; + + for (var i = 0; i < entries.count; i++) + { + var e = entries[i]; + + if (e.low < _yMin) + { + _yMin = e.low; + } + + if (e.high > _yMax) + { + _yMax = e.high; + } + } + } + + /// the space that is left out on the left and right side of each candle, + /// :default: 0.1 (10%), max 0.45, min 0.0 + public var bodySpace: CGFloat + { + set + { + _bodySpace = newValue; + + if (_bodySpace < 0.0) + { + _bodySpace = 0.0; + } + if (_bodySpace > 0.45) + { + _bodySpace = 0.45; + } + } + get + { + return _bodySpace; + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/ChartData.swift b/Charts/Classes/Data/ChartData.swift new file mode 100644 index 0000000000..7ee79d6251 --- /dev/null +++ b/Charts/Classes/Data/ChartData.swift @@ -0,0 +1,831 @@ +// +// ChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartData: NSObject +{ + internal var _yMax = Float(0.0) + internal var _yMin = Float(0.0) + internal var _leftAxisMax = Float(0.0) + internal var _leftAxisMin = Float(0.0) + internal var _rightAxisMax = Float(0.0) + internal var _rightAxisMin = Float(0.0) + private var _yValueSum = Float(0.0) + private var _yValCount = Int(0) + + /// the average length (in characters) across all x-value strings + private var _xValAverageLength = Float(0.0) + + internal var _xVals: [String]! + internal var _dataSets: [ChartDataSet]! + + public override init() + { + super.init(); + + _xVals = [String](); + _dataSets = [ChartDataSet](); + } + + public init(xVals: [String]?) + { + super.init(); + + _xVals = xVals == nil ? [String]() : xVals; + _dataSets = [ChartDataSet](); + + self.initialize(_dataSets); + } + + public convenience init(xVals: [String]?, dataSet: ChartDataSet?) + { + self.init(xVals: xVals, dataSets: dataSet === nil ? nil : [dataSet!]); + } + + public init(xVals: [String]?, dataSets: [ChartDataSet]?) + { + super.init() + + _xVals = xVals == nil ? [String]() : xVals; + _dataSets = dataSets == nil ? [ChartDataSet]() : dataSets; + + self.initialize(_dataSets) + } + + internal func initialize(dataSets: [ChartDataSet]) + { + checkIsLegal(dataSets); + + calcMinMax(); + calcYValueSum(); + calcYValueCount(); + + calcXValAverageLength() + } + + // calculates the average length (in characters) across all x-value strings + internal func calcXValAverageLength() + { + if (_xVals.count == 0) + { + _xValAverageLength = 1; + return; + } + + var sum = 1; + + for (var i = 0; i < _xVals.count; i++) + { + sum += _xVals[i].lengthOfBytesUsingEncoding(NSUTF16StringEncoding); + } + + _xValAverageLength = Float(sum) / Float(_xVals.count); + } + + // Checks if the combination of x-values array and DataSet array is legal or not. + // :param: dataSets + internal func checkIsLegal(dataSets: [ChartDataSet]!) + { + if (dataSets == nil) + { + return; + } + + for (var i = 0; i < dataSets.count; i++) + { + if (dataSets[i].yVals.count > _xVals.count) + { + println("One or more of the DataSet Entry arrays are longer than the x-values array of this Data object."); + return; + } + } + } + + public func notifyDataChanged() + { + initialize(_dataSets); + } + + /// calc minimum and maximum y value over all datasets + internal func calcMinMax() + { + if (_dataSets == nil || _dataSets.count < 1) + { + _yMax = 0.0; + _yMin = 0.0; + } + else + { + // calculate absolute min and max + _yMin = _dataSets[0].yMin; + _yMax = _dataSets[0].yMax; + + for (var i = 0; i < _dataSets.count; i++) + { + if (_dataSets[i].yMin < _yMin) + { + _yMin = _dataSets[i].yMin; + } + + if (_dataSets[i].yMax > _yMax) + { + _yMax = _dataSets[i].yMax; + } + } + + // left axis + var firstLeft = getFirstLeft(); + + if (firstLeft !== nil) + { + _leftAxisMax = firstLeft!.yMax; + _leftAxisMin = firstLeft!.yMin; + + for dataSet in _dataSets + { + if (dataSet.axisDependency == .Left) + { + if (dataSet.yMin < _leftAxisMin) + { + _leftAxisMin = dataSet.yMin; + } + + if (dataSet.yMax > _leftAxisMax) + { + _leftAxisMax = dataSet.yMax; + } + } + } + } + + // right axis + var firstRight = getFirstRight(); + + if (firstRight !== nil) + { + _rightAxisMax = firstRight!.yMax; + _rightAxisMin = firstRight!.yMin; + + for dataSet in _dataSets + { + if (dataSet.axisDependency == .Right) + { + if (dataSet.yMin < _rightAxisMin) + { + _rightAxisMin = dataSet.yMin; + } + + if (dataSet.yMax > _rightAxisMax) + { + _rightAxisMax = dataSet.yMax; + } + } + } + } + + // in case there is only one axis, adjust the second axis + handleEmptyAxis(firstLeft, firstRight: firstRight); + } + } + + /// calculates the sum of all y-values in all datasets + internal func calcYValueSum() + { + _yValueSum = 0; + + if (_dataSets == nil) + { + return; + } + + for (var i = 0; i < _dataSets.count; i++) + { + _yValueSum += fabsf(_dataSets[i].yValueSum); + } + } + + /// Calculates the total number of y-values across all ChartDataSets the ChartData represents. + internal func calcYValueCount() + { + _yValCount = 0; + + if (_dataSets == nil) + { + return; + } + + var count = 0; + + for (var i = 0; i < _dataSets.count; i++) + { + count += _dataSets[i].entryCount; + } + + _yValCount = count; + } + + /// returns the number of LineDataSets this object contains + public var dataSetCount: Int + { + if (_dataSets == nil) + { + return 0; + } + return _dataSets.count; + } + + /// returns the smallest y-value the data object contains. + public var yMin: Float + { + return _yMin; + } + + public func getYMin() -> Float + { + return _yMin; + } + + public func getYMin(axis: ChartYAxis.AxisDependency) -> Float + { + if (axis == .Left) + { + return _leftAxisMin; + } + else + { + return _rightAxisMin; + } + } + + /// returns the greatest y-value the data object contains. + public var yMax: Float + { + return _yMax; + } + + public func getYMax() -> Float + { + return _yMax; + } + + public func getYMax(axis: ChartYAxis.AxisDependency) -> Float + { + if (axis == .Left) + { + return _leftAxisMax; + } + else + { + return _rightAxisMax; + } + } + + /// returns the average length (in characters) across all values in the x-vals array + public var xValAverageLength: Float + { + return _xValAverageLength; + } + + /// returns the total y-value sum across all DataSet objects the this object represents. + public var yValueSum: Float + { + return _yValueSum; + } + + /// Returns the total number of y-values across all DataSet objects the this object represents. + public var yValCount: Int + { + return _yValCount; + } + + /// returns the x-values the chart represents + public var xVals: [String] + { + return _xVals; + } + + ///Adds a new x-value to the chart data. + public func addXValue(xVal: String) + { + _xVals.append(xVal); + } + + /// Removes the x-value at the specified index. + public func removeXValue(index: Int) + { + _xVals.removeAtIndex(index); + } + + /// Returns the array of ChartDataSets this object holds. + public var dataSets: [ChartDataSet] + { + get + { + return _dataSets; + } + set + { + _dataSets = newValue; + } + } + + /// Retrieve the index of a ChartDataSet with a specific label from the ChartData. Search can be case sensitive or not. + /// IMPORTANT: This method does calculations at runtime, do not over-use in performance critical situations. + /// + /// :param: dataSets the DataSet array to search + /// :param: type + /// :param: ignorecase if true, the search is not case-sensitive + /// :returns: + internal func getDataSetIndexByLabel(label: String, ignorecase: Bool) -> Int + { + if (ignorecase) + { + for (var i = 0; i < dataSets.count; i++) + { + if (label.caseInsensitiveCompare(dataSets[i].label) == NSComparisonResult.OrderedSame) + { + return i; + } + } + } + else + { + for (var i = 0; i < dataSets.count; i++) + { + if (label == dataSets[i].label) + { + return i; + } + } + } + + return -1; + } + + /// returns the total number of x-values this ChartData object represents (the size of the x-values array) + public var xValCount: Int + { + return _xVals.count; + } + + /// Returns the labels of all DataSets as a string array. + internal func dataSetLabels() -> [String] + { + var types = [String](); + + for (var i = 0; i < _dataSets.count; i++) + { + types[i] = _dataSets[i].label; + } + + return types; + } + + /// Get the Entry for a corresponding highlight object + /// + /// :param: highlight + /// :returns: the entry that is highlighted + public func getEntryForHighlight(highlight: ChartHighlight) -> ChartDataEntry + { + return _dataSets[highlight.dataSetIndex].entryForXIndex(highlight.xIndex); + } + + /// Returns the DataSet object with the given label. + /// sensitive or not. + /// IMPORTANT: This method does calculations at runtime. Use with care in performance critical situations. + /// + /// :param: label + /// :param: ignorecase + public func getDataSetByLabel(label: String, ignorecase: Bool) -> ChartDataSet? + { + var index = getDataSetIndexByLabel(label, ignorecase: ignorecase); + + if (index < 0 || index >= _dataSets.count) + { + return nil; + } + else + { + return _dataSets[index]; + } + } + + public func getDataSetByIndex(index: Int) -> ChartDataSet! + { + if (_dataSets == nil || index < 0 || index >= _dataSets.count) + { + return nil; + } + + return _dataSets[index]; + } + + public func addDataSet(d: ChartDataSet!) + { + if (_dataSets == nil) + { + return; + } + + _yValCount += d.entryCount; + _yValueSum += d.yValueSum; + + if (_dataSets.count == 0) + { + _yMax = d.yMax; + _yMin = d.yMin; + + if (d.axisDependency == .Left) + { + _leftAxisMax = d.yMax; + _leftAxisMin = d.yMin; + } + else + { + _rightAxisMax = d.yMax; + _rightAxisMin = d.yMin; + } + } + else + { + if (_yMax < d.yMax) + { + _yMax = d.yMax; + } + if (_yMin > d.yMin) + { + _yMin = d.yMin; + } + + if (d.axisDependency == .Left) + { + if (_leftAxisMax < d.yMax) + { + _leftAxisMax = d.yMax; + } + if (_leftAxisMin > d.yMin) + { + _leftAxisMin = d.yMin; + } + } + else + { + if (_rightAxisMax < d.yMax) + { + _rightAxisMax = d.yMax; + } + if (_rightAxisMin > d.yMin) + { + _rightAxisMin = d.yMin; + } + } + } + + _dataSets.append(d); + + handleEmptyAxis(getFirstLeft(), firstRight: getFirstRight()); + } + + public func handleEmptyAxis(firstLeft: ChartDataSet?, firstRight: ChartDataSet?) + { + // in case there is only one axis, adjust the second axis + if (firstLeft === nil) + { + _leftAxisMax = _rightAxisMax; + _leftAxisMin = _rightAxisMin; + } + else if (firstRight === nil) + { + _rightAxisMax = _leftAxisMax; + _rightAxisMin = _leftAxisMin; + } + } + + /// Removes the given DataSet from this data object. + /// Also recalculates all minimum and maximum values. + /// + /// :returns: true if a DataSet was removed, false if no DataSet could be removed. + public func removeDataSet(dataSet: ChartDataSet!) -> Bool + { + if (_dataSets == nil || dataSet === nil) + { + return false; + } + + var removed = false; + for (var i = 0; i < _dataSets.count; i++) + { + if (_dataSets[i] === dataSet) + { + return removeDataSetByIndex(i); + } + } + + return false; + } + + /// Removes the DataSet at the given index in the DataSet array from the data object. + /// Also recalculates all minimum and maximum values. + /// + /// :returns: true if a DataSet was removed, false if no DataSet could be removed. + public func removeDataSetByIndex(index: Int) -> Bool + { + if (_dataSets == nil || index >= _dataSets.count || index < 0) + { + return false; + } + + var d = _dataSets.removeAtIndex(index); + _yValCount -= d.entryCount; + _yValueSum -= d.yValueSum; + + calcMinMax(); + + return true; + } + + /// Adds an Entry to the DataSet at the specified index. Entries are added to the end of the list. + public func addEntry(e: ChartDataEntry, dataSetIndex: Int) + { + if (_dataSets != nil && _dataSets.count > dataSetIndex && dataSetIndex >= 0) + { + var val = e.value; + + _yValCount += 1; + _yValueSum += val; + + if (_yMax < val) + { + _yMax = val; + } + if (_yMin > val) + { + _yMin = val; + } + + var set = _dataSets[dataSetIndex]; + if (set.axisDependency == .Left) + { + if (_leftAxisMax < e.value) + { + _leftAxisMax = e.value; + } + if (_leftAxisMin > e.value) + { + _leftAxisMin = e.value; + } + } + else + { + if (_rightAxisMax < e.value) + { + _rightAxisMax = e.value; + } + if (_rightAxisMin > e.value) + { + _rightAxisMin = e.value; + } + } + + handleEmptyAxis(getFirstLeft(), firstRight: getFirstRight()); + + set.addEntry(e); + } + else + { + println("ChartData.addEntry() - dataSetIndex our of range."); + } + } + + /// Removes the given Entry object from the DataSet at the specified index. + public func removeEntry(entry: ChartDataEntry!, dataSetIndex: Int) -> Bool + { + // entry null, outofbounds + if (entry === nil || dataSetIndex >= _dataSets.count) + { + return false; + } + + // remove the entry from the dataset + var removed = _dataSets[dataSetIndex].removeEntry(xIndex: entry.xIndex); + + if (removed) + { + var val = entry.value; + + _yValCount -= 1; + _yValueSum -= val; + + calcMinMax(); + } + + return removed; + } + + /// Removes the Entry object at the given xIndex from the ChartDataSet at the + /// specified index. Returns true if an entry was removed, false if no Entry + /// was found that meets the specified requirements. + public func removeEntryByXIndex(xIndex: Int, dataSetIndex: Int) -> Bool + { + if (dataSetIndex >= _dataSets.count) + { + return false; + } + + var entry = _dataSets[dataSetIndex].entryForXIndex(xIndex); + + return removeEntry(entry, dataSetIndex: dataSetIndex); + } + + /// Returns the DataSet that contains the provided Entry, or null, if no DataSet contains this entry. + public func getDataSetForEntry(e: ChartDataEntry!) -> ChartDataSet? + { + if (e == nil) + { + return nil; + } + + for (var i = 0; i < _dataSets.count; i++) + { + var set = _dataSets[i]; + + for (var j = 0; j < set.entryCount; j++) + { + if (e === set.entryForXIndex(e.xIndex)) + { + return set; + } + } + } + + return nil; + } + + /// Returns the index of the provided DataSet inside the DataSets array of + /// this data object. Returns -1 if the DataSet was not found. + public func indexOfDataSet(dataSet: ChartDataSet) -> Int + { + for (var i = 0; i < _dataSets.count; i++) + { + if (_dataSets[i] === dataSet) + { + return i; + } + } + + return -1; + } + + public func getFirstLeft() -> ChartDataSet? + { + for dataSet in _dataSets + { + if (dataSet.axisDependency == .Left) + { + return dataSet; + } + } + + return nil; + } + + public func getFirstRight() -> ChartDataSet? + { + for dataSet in _dataSets + { + if (dataSet.axisDependency == .Right) + { + return dataSet; + } + } + + return nil; + } + + /// Returns all colors used across all DataSet objects this object represents. + public func getColors() -> [UIColor]? + { + if (_dataSets == nil) + { + return nil; + } + + var clrcnt = 0; + + for (var i = 0; i < _dataSets.count; i++) + { + clrcnt += _dataSets[i].colors.count; + } + + var colors = [UIColor](); + + for (var i = 0; i < _dataSets.count; i++) + { + var clrs = _dataSets[i].colors; + + for clr in clrs + { + colors.append(clr); + } + } + + return colors; + } + + /// Generates an x-values array filled with numbers in range specified by the parameters. Can be used for convenience. + public func generateXVals(from: Int, to: Int) -> [String] + { + var xvals = [String](); + + for (var i = from; i < to; i++) + { + xvals.append(String(i)); + } + + return xvals; + } + + /// Sets a custom ValueFormatter for all DataSets this data object contains. + public func setValueFormatter(formatter: NSNumberFormatter!) + { + for set in dataSets + { + set.valueFormatter = formatter; + } + } + + /// Sets the color of the value-text (color in which the value-labels are drawn) for all DataSets this data object contains. + public func setValueTextColor(color: UIColor!) + { + for set in dataSets + { + set.valueTextColor = color ?? set.valueTextColor; + } + } + + /// Sets the font for all value-labels for all DataSets this data object contains. + public func setValueFont(font: UIFont!) + { + for set in dataSets + { + set.valueFont = font ?? set.valueFont; + } + } + + /// Enables / disables drawing values (value-text) for all DataSets this data object contains. + public func setDrawValues(enabled: Bool) + { + for set in dataSets + { + set.drawValuesEnabled = enabled; + } + } + + /// Clears this data object from all DataSets and removes all Entries. + public func clearValues() + { + dataSets.removeAll(keepCapacity: false); + notifyDataChanged(); + } + + /// Checks if this data object contains the specified Entry. Returns true if so, false if not. + public func contains(#entry: ChartDataEntry) -> Bool + { + for set in dataSets + { + if (set.contains(entry)) + { + return true; + } + } + + return false; + } + + /// Checks if this data object contains the specified DataSet. Returns true if so, false if not. + public func contains(#dataSet: ChartDataSet) -> Bool + { + for set in dataSets + { + if (set.isEqual(dataSet)) + { + return true; + } + } + + return false; + } +} diff --git a/Charts/Classes/Data/ChartDataEntry.swift b/Charts/Classes/Data/ChartDataEntry.swift new file mode 100644 index 0000000000..e4913d5765 --- /dev/null +++ b/Charts/Classes/Data/ChartDataEntry.swift @@ -0,0 +1,129 @@ +// +// ChartDataEntry.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartDataEntry: NSObject, Equatable +{ + /// the actual value (y axis) + public var value = Float(0.0) + + /// the index on the x-axis + public var xIndex = Int(0) + + /// optional spot for additional data this Entry represents + public var data: AnyObject? + + public override init() + { + super.init(); + } + + public init(value: Float, xIndex: Int) + { + super.init(); + + self.value = value; + self.xIndex = xIndex; + } + + public init(value: Float, xIndex: Int, data: AnyObject?) + { + super.init(); + + self.value = value; + self.xIndex = xIndex; + self.data = data; + } + + // MARK: NSObject + + public override func isEqual(object: AnyObject?) -> Bool + { + if (object == nil) + { + return false; + } + + if (!object!.isKindOfClass(self.dynamicType)) + { + return false; + } + + if (object!.data !== data && !object!.isEqual(self)) + { + return false; + } + + if (object!.xIndex != xIndex) + { + return false; + } + + if (fabsf(object!.value - value) > 0.00001) + { + return false; + } + + return true; + } + + // MARK: NSObject + + public override var description: String + { + return "ChartDataEntry, xIndex: \(xIndex), value \(value)"; + } + + // MARK: NSCopying + + public func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = self.dynamicType.allocWithZone(zone) as ChartDataEntry; + copy.value = value; + copy.xIndex = xIndex; + copy.data = data; + return copy; + } +} + +public func ==(lhs: ChartDataEntry, rhs: ChartDataEntry) -> Bool +{ + if (lhs === rhs) + { + return true; + } + + if (!lhs.isKindOfClass(rhs.dynamicType)) + { + return false; + } + + if (lhs.data !== rhs.data && !lhs.data!.isEqual(rhs.data)) + { + return false; + } + + if (lhs.xIndex != rhs.xIndex) + { + return false; + } + + if (fabsf(lhs.value - rhs.value) > 0.00001) + { + return false; + } + + return true; +} \ No newline at end of file diff --git a/Charts/Classes/Data/ChartDataSet.swift b/Charts/Classes/Data/ChartDataSet.swift new file mode 100644 index 0000000000..568bd73039 --- /dev/null +++ b/Charts/Classes/Data/ChartDataSet.swift @@ -0,0 +1,390 @@ +// +// ChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartDataSet: NSObject +{ + public var colors = [UIColor]() + internal var _yVals: [ChartDataEntry]! + internal var _yMax = Float(0.0) + internal var _yMin = Float(0.0) + internal var _yValueSum = Float(0.0) + internal var _label = "DataSet" + public var visible = true; + public var drawValuesEnabled = true; + + /// the color used for the value-text + public var valueTextColor: UIColor = UIColor.blackColor() + + /// the font for the value-text labels + public var valueFont: UIFont = UIFont.systemFontOfSize(7.0) + + /// the formatter used to customly format the values + public var valueFormatter: NSNumberFormatter? + + /// the axis this DataSet should be plotted against. + public var axisDependency = ChartYAxis.AxisDependency.Left + + public var yVals: [ChartDataEntry] { return _yVals } + public var yValueSum: Float { return _yValueSum } + public var yMin: Float { return _yMin } + public var yMax: Float { return _yMax } + public var label: String { return _label } + + public override init() + { + super.init(); + } + + public init(yVals: [ChartDataEntry]?, label: String) + { + super.init(); + + _label = label; + _yVals = yVals == nil ? [ChartDataEntry]() : yVals; + + // default color + colors.append(UIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)); + + self.calcMinMax(); + self.calcYValueSum(); + } + + public convenience init(yVals: [ChartDataEntry]?) + { + self.init(yVals: yVals, label: "DataSet") + } + + internal func calcMinMax() + { + if _yVals!.count == 0 + { + return; + } + + _yMin = yVals[0].value; + _yMax = yVals[0].value; + + for var i = 0; i < _yVals.count; i++ + { + let e = _yVals[i]; + if (e.value < _yMin) + { + _yMin = e.value; + } + if (e.value > _yMax) + { + _yMax = e.value; + } + } + } + + private func calcYValueSum() + { + _yValueSum = 0; + + for var i = 0; i < _yVals.count; i++ + { + _yValueSum += fabsf(_yVals[i].value); + } + } + + public var entryCount: Int { return _yVals!.count; } + + public func yValForXIndex(x: Int) -> Float + { + let e = self.entryForXIndex(x); + + if (e !== nil) { return e.value } + else { return Float.NaN } + } + + /// Returns the first Entry object found at the given xIndex with binary search. + /// If the no Entry at the specifed x-index is found, this method returns the Entry at the closest x-index. + /// Returns nil if no Entry object at that index. + public func entryForXIndex(x: Int) -> ChartDataEntry! + { + var index = self.entryIndex(xIndex: x); + if (index > -1) + { + return _yVals[index]; + } + return nil; + } + + public func entriesForXIndex(x: Int) -> [ChartDataEntry] + { + var entries = [ChartDataEntry](); + + var low = 0; + var high = _yVals.count - 1; + + while (low <= high) + { + var m = Int((high + low) / 2); + var entry = _yVals[m]; + + if (x == entry.xIndex) + { + while (m > 0 && _yVals[m - 1].xIndex == x) + { + m--; + } + + high = _yVals.count; + for (; m < high; m++) + { + entry = _yVals[m]; + if (entry.xIndex == x) + { + entries.append(entry); + } + else + { + break; + } + } + } + + if (x > _yVals[m].xIndex) + { + low = m + 1; + } + else + { + high = m - 1; + } + } + + return entries; + } + + public func entryIndex(xIndex x: Int) -> Int + { + var low = 0; + var high = _yVals.count - 1; + var closest = -1; + + while (low <= high) + { + var m = (high + low) / 2; + var entry = _yVals[m]; + + if (x == entry.xIndex) + { + while (m > 0 && _yVals[m - 1].xIndex == x) + { + m--; + } + + return m; + } + + if (x > entry.xIndex) + { + low = m + 1; + } + else + { + high = m - 1; + } + + closest = m; + } + + return closest; + } + + public func entryIndex(entry e: ChartDataEntry, isEqual: Bool) -> Int + { + if (isEqual) + { + for (var i = 0; i < _yVals.count; i++) + { + if (_yVals[i].isEqual(e)) + { + return i; + } + } + } + else + { + for (var i = 0; i < _yVals.count; i++) + { + if (_yVals[i] === e) + { + return i; + } + } + } + + return -1 + } + + /// Returns the number of entries this DataSet holds. + public var valueCount: Int { return _yVals.count; } + + public func addEntry(e: ChartDataEntry) + { + var val = e.value; + + if (_yVals == nil || _yVals.count <= 0) + { + _yVals = [ChartDataEntry](); + _yMax = val; + _yMin = val; + } + else + { + if (_yMax < val) + { + _yMax = val; + } + if (_yMin > val) + { + _yMin = val; + } + } + + _yValueSum += val; + + _yVals.append(e); + } + + public func removeEntry(entry: ChartDataEntry) -> Bool + { + var removed = false; + + for (var i = 0; i < _yVals.count; i++) + { + if (_yVals[i] === entry) + { + _yVals.removeAtIndex(i); + removed = true; + break; + } + } + + if (removed) + { + _yValueSum -= entry.value; + calcMinMax(); + } + + return removed; + } + + public func removeEntry(#xIndex: Int) -> Bool + { + var index = self.entryIndex(xIndex: xIndex); + if (index > -1) + { + var e = _yVals.removeAtIndex(index); + + _yValueSum -= e.value; + calcMinMax(); + + return true; + } + + return false; + } + + public func resetColors() + { + colors.removeAll(keepCapacity: false); + } + + public func addColor(color: UIColor) + { + colors.append(color); + } + + public func setColor(color: UIColor) + { + colors.removeAll(keepCapacity: false); + colors.append(color); + } + + public func colorAt(var index: Int) -> UIColor + { + if (index < 0) + { + index = 0; + } + return colors[index % colors.count]; + } + + public var isVisible: Bool + { + return visible; + } + + public var isDrawValuesEnabled: Bool + { + return drawValuesEnabled; + } + + /// Checks if this DataSet contains the specified Entry. + /// :returns: true if contains the entry, false if not. + public func contains(e: ChartDataEntry) -> Bool + { + for entry in _yVals + { + if (entry.isEqual(e)) + { + return true; + } + } + + return false; + } + + // MARK: NSObject + + public override var description: String + { + return String(format: "ChartDataSet, label: %@, %i entries", arguments: [_label, _yVals.count]); + } + + public override var debugDescription: String + { + var desc = description + ":"; + + for (var i = 0; i < _yVals.count; i++) + { + desc += "\n" + _yVals[i].description; + } + + return desc; + } + + // MARK: NSCopying + + public func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = self.dynamicType.allocWithZone(zone) as ChartDataSet; + copy.colors = colors; + copy._yVals = _yVals; + copy._yMax = _yMax; + copy._yMin = _yMin; + copy._yValueSum = _yValueSum; + copy._label = _label; + return copy; + } +} + + diff --git a/Charts/Classes/Data/CombinedChartData.swift b/Charts/Classes/Data/CombinedChartData.swift new file mode 100644 index 0000000000..1065bbe3e6 --- /dev/null +++ b/Charts/Classes/Data/CombinedChartData.swift @@ -0,0 +1,136 @@ +// +// CombinedChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class CombinedChartData: BarLineScatterCandleChartData +{ + private var _lineData: LineChartData! + private var _barData: BarChartData! + private var _scatterData: ScatterChartData! + private var _candleData: CandleChartData! + + public override init() + { + super.init(); + } + + public override init(xVals: [String]?) + { + super.init(xVals: xVals); + } + + public var lineData: LineChartData! + { + get + { + return _lineData; + } + set + { + _lineData = newValue; + for dataSet in newValue.dataSets + { + _dataSets.append(dataSet); + } + + checkIsLegal(newValue.dataSets); + + calcMinMax(); + calcYValueSum(); + calcYValueCount(); + + calcXValAverageLength(); + } + } + + public var barData: BarChartData! + { + get + { + return _barData; + } + set + { + _barData = newValue; + for dataSet in newValue.dataSets + { + _dataSets.append(dataSet); + } + + checkIsLegal(newValue.dataSets); + + calcMinMax(); + calcYValueSum(); + calcYValueCount(); + + calcXValAverageLength(); + } + } + + public var scatterData: ScatterChartData! + { + get + { + return _scatterData; + } + set + { + _scatterData = newValue; + for dataSet in newValue.dataSets + { + _dataSets.append(dataSet); + } + + checkIsLegal(newValue.dataSets); + + calcMinMax(); + calcYValueSum(); + calcYValueCount(); + + calcXValAverageLength(); + } + } + + public var candleData: CandleChartData! + { + get + { + return _candleData; + } + set + { + _candleData = newValue; + for dataSet in newValue.dataSets + { + _dataSets.append(dataSet); + } + + checkIsLegal(newValue.dataSets); + + calcMinMax(); + calcYValueSum(); + calcYValueCount(); + + calcXValAverageLength(); + } + } + + public override func notifyDataChanged() + { + _lineData.notifyDataChanged(); + _barData.notifyDataChanged(); + _candleData.notifyDataChanged(); + _scatterData.notifyDataChanged(); + } +} diff --git a/Charts/Classes/Data/LineChartData.swift b/Charts/Classes/Data/LineChartData.swift new file mode 100644 index 0000000000..9ecad10fbd --- /dev/null +++ b/Charts/Classes/Data/LineChartData.swift @@ -0,0 +1,20 @@ +// +// LineChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Data object that encapsulates all data associated with a LineChart. +public class LineChartData: ChartData +{ + +} diff --git a/Charts/Classes/Data/LineChartDataSet.swift b/Charts/Classes/Data/LineChartDataSet.swift new file mode 100644 index 0000000000..bce103810c --- /dev/null +++ b/Charts/Classes/Data/LineChartDataSet.swift @@ -0,0 +1,116 @@ +// +// LineChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class LineChartDataSet: LineRadarChartDataSet +{ + public var circleColors = [UIColor]() + public var circleHoleColor = UIColor.whiteColor() + public var circleRadius = CGFloat(8.0) + + private var _cubicIntensity = CGFloat(0.2) + + public var lineDashPhase = CGFloat(0.0) + public var lineDashLengths: [CGFloat]! + + /// if true, drawing circles is enabled + public var drawCirclesEnabled = true + + /// if true, cubic lines are drawn instead of linear + public var drawCubicEnabled = false + + public var drawCircleHoleEnabled = true; + + public override init() + { + super.init(); + circleColors.append(UIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)); + } + + public override init(yVals: [ChartDataEntry]?, label: String) + { + super.init(yVals: yVals, label: label); + circleColors.append(UIColor(red: 140.0, green: 234.0, blue: 255.0, alpha: 1.0)); + } + + /// intensity for cubic lines (min = 0.05f, max = 1f) + /// :default: 0.2 + public var cubicIntensity: CGFloat + { + get + { + return _cubicIntensity; + } + set + { + _cubicIntensity = newValue; + if (_cubicIntensity > 1.0) + { + _cubicIntensity = 1.0; + } + if (_cubicIntensity < 0.05) + { + _cubicIntensity = 0.05; + } + } + } + + /// Returns the color at the given index of the DataSet's circle-color array. + /// Performs a IndexOutOfBounds check by modulus. + public func getCircleColor(var index: Int) -> UIColor? + { + let size = circleColors.count; + index = index % size; + if (index >= size) + { + return nil; + } + return circleColors[index]; + } + + /// Sets the one and ONLY color that should be used for this DataSet. + /// Internally, this recreates the colors array and adds the specified color. + public func setCircleColor(color: UIColor) + { + circleColors.removeAll(keepCapacity: false); + circleColors.append(color); + } + + /// resets the circle-colors array and creates a new one + public func resetCircleColors(var index: Int) + { + circleColors.removeAll(keepCapacity: false); + } + + public var isDrawCirclesEnabled: Bool { return drawCirclesEnabled; } + + public var isDrawCubicEnabled: Bool { return drawCubicEnabled; } + + public var isDrawCircleHoleEnabled: Bool { return drawCircleHoleEnabled; } + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! LineChartDataSet; + copy.circleColors = circleColors; + copy.circleRadius = circleRadius; + copy.cubicIntensity = cubicIntensity; + copy.lineDashPhase = lineDashPhase; + copy.lineDashLengths = lineDashLengths; + copy.drawCirclesEnabled = drawCirclesEnabled; + copy.drawCubicEnabled = drawCubicEnabled; + return copy; + } +} diff --git a/Charts/Classes/Data/LineRadarChartDataSet.swift b/Charts/Classes/Data/LineRadarChartDataSet.swift new file mode 100644 index 0000000000..fbd632badf --- /dev/null +++ b/Charts/Classes/Data/LineRadarChartDataSet.swift @@ -0,0 +1,60 @@ +// +// LineRadarChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class LineRadarChartDataSet: BarLineScatterCandleChartDataSet +{ + public var fillColor = UIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0) + public var fillAlpha = CGFloat(0.33) + private var _lineWidth = CGFloat(1.0) + public var drawFilledEnabled = false + + /// line width of the chart (min = 0.2f, max = 10f) + /// :default: 1 + public var lineWidth: CGFloat + { + get + { + return _lineWidth; + } + set + { + _lineWidth = newValue; + if (_lineWidth < 0.2) + { + _lineWidth = 0.5; + } + if (_lineWidth > 10.0) + { + _lineWidth = 10.0; + } + } + } + + public var isDrawFilledEnabled: Bool + { + return drawFilledEnabled; + } + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! LineRadarChartDataSet; + copy.fillColor = fillColor; + copy._lineWidth = _lineWidth; + copy.drawFilledEnabled = drawFilledEnabled; + return copy; + } +} diff --git a/Charts/Classes/Data/PieChartData.swift b/Charts/Classes/Data/PieChartData.swift new file mode 100644 index 0000000000..f1b82d9fbf --- /dev/null +++ b/Charts/Classes/Data/PieChartData.swift @@ -0,0 +1,75 @@ +// +// PieData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 24/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class PieChartData: ChartData +{ + public override init() + { + super.init(); + + } + + var dataSet: PieChartDataSet? + { + get + { + return dataSets.count > 0 ? dataSets[0] as? PieChartDataSet : nil; + } + set + { + if (newValue != nil) + { + dataSets = [newValue!]; + } + else + { + dataSets = []; + } + } + } + + public override func getDataSetByIndex(index: Int) -> ChartDataSet? + { + if (index != 0) + { + return nil; + } + return super.getDataSetByIndex(index); + } + + public override func getDataSetByLabel(label: String, ignorecase: Bool) -> ChartDataSet? + { + if (dataSets.count == 0) + { + return nil; + } + + if (ignorecase) + { + if (label.caseInsensitiveCompare(dataSets[0].label) == NSComparisonResult.OrderedSame) + { + return dataSets[0]; + } + } + else + { + if (label == dataSets[0].label) + { + return dataSets[0]; + } + } + return nil; + } +} diff --git a/Charts/Classes/Data/PieChartDataSet.swift b/Charts/Classes/Data/PieChartDataSet.swift new file mode 100644 index 0000000000..2adcfec7ea --- /dev/null +++ b/Charts/Classes/Data/PieChartDataSet.swift @@ -0,0 +1,71 @@ +// +// PieChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 24/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class PieChartDataSet: ChartDataSet +{ + private var _sliceSpace = CGFloat(0.0) + + /// indicates the selection distance of a pie slice + public var selectionShift = CGFloat(18.0) + + public override init() + { + super.init(); + + self.valueTextColor = UIColor.whiteColor(); + self.valueFont = UIFont.systemFontOfSize(13.0); + } + + public override init(yVals: [ChartDataEntry]?, label: String) + { + super.init(yVals: yVals, label: label); + + self.valueTextColor = UIColor.whiteColor(); + self.valueFont = UIFont.systemFontOfSize(13.0); + } + + /// the space that is left out between the piechart-slices, default: 0° + /// --> no space, maximum 45, minimum 0 (no space) + public var sliceSpace: CGFloat + { + get + { + return _sliceSpace; + } + set + { + _sliceSpace = newValue; + if (_sliceSpace > 45.0) + { + _sliceSpace = 45.0; + } + if (_sliceSpace < 0.0) + { + _sliceSpace = 0.0; + } + } + } + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! PieChartDataSet; + copy._sliceSpace = _sliceSpace; + copy.selectionShift = selectionShift; + return copy; + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/RadarChartData.swift b/Charts/Classes/Data/RadarChartData.swift new file mode 100644 index 0000000000..ec3a2a009b --- /dev/null +++ b/Charts/Classes/Data/RadarChartData.swift @@ -0,0 +1,28 @@ +// +// RadarChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class RadarChartData: ChartData +{ + public var highlightColor = UIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0) + public var highlightLineWidth = CGFloat(1.0) + public var highlightLineDashPhase = CGFloat(0.0) + public var highlightLineDashLengths: [CGFloat]? + + internal override func initialize(dataSets: [ChartDataSet]) + { + super.initialize(dataSets); + + } +} diff --git a/Charts/Classes/Data/RadarChartDataSet.swift b/Charts/Classes/Data/RadarChartDataSet.swift new file mode 100644 index 0000000000..a408c49d49 --- /dev/null +++ b/Charts/Classes/Data/RadarChartDataSet.swift @@ -0,0 +1,32 @@ +// +// RadarChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 24/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class RadarChartDataSet: LineRadarChartDataSet +{ + public override init() + { + super.init(); + + self.valueFont = UIFont.systemFontOfSize(13.0); + } + + public override init(yVals: [ChartDataEntry]?, label: String) + { + super.init(yVals: yVals, label: label); + + self.valueFont = UIFont.systemFontOfSize(13.0); + } +} \ No newline at end of file diff --git a/Charts/Classes/Data/ScatterChartData.swift b/Charts/Classes/Data/ScatterChartData.swift new file mode 100644 index 0000000000..667363a6e2 --- /dev/null +++ b/Charts/Classes/Data/ScatterChartData.swift @@ -0,0 +1,44 @@ +// +// ScatterChartData.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ScatterChartData: BarLineScatterCandleChartData +{ + /// Returns the maximum shape-size across all DataSets. + public func getGreatestShapeSize() -> CGFloat + { + var max = CGFloat(0.0); + + for set in _dataSets + { + let scatterDataSet = set as? ScatterChartDataSet; + + if (scatterDataSet == nil) + { + println("ScatterChartData: Found a DataSet which is not a ScatterChartDataSet"); + } + else + { + let size = scatterDataSet!.scatterShapeSize; + + if (size > max) + { + max = size; + } + } + } + + return max; + } +} diff --git a/Charts/Classes/Data/ScatterChartDataSet.swift b/Charts/Classes/Data/ScatterChartDataSet.swift new file mode 100644 index 0000000000..9d87e92405 --- /dev/null +++ b/Charts/Classes/Data/ScatterChartDataSet.swift @@ -0,0 +1,43 @@ +// +// ScatterChartDataSet.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import CoreGraphics; + +public class ScatterChartDataSet: BarLineScatterCandleChartDataSet +{ + @objc + public enum ScatterShape: Int + { + case Cross + case Triangle + case Circle + case Square + case Custom + } + + public var scatterShapeSize = CGFloat(15.0) + public var scatterShape = ScatterShape.Square + public var customScatterShape: CGPath? + + // MARK: NSCopying + + public override func copyWithZone(zone: NSZone) -> AnyObject + { + var copy = super.copyWithZone(zone) as! ScatterChartDataSet; + copy.scatterShapeSize = scatterShapeSize; + copy.scatterShape = scatterShape; + copy.customScatterShape = customScatterShape; + return copy; + } +} diff --git a/Charts/Classes/Filters/ChartDataApproximatorFilter.swift b/Charts/Classes/Filters/ChartDataApproximatorFilter.swift new file mode 100644 index 0000000000..a0b1428e55 --- /dev/null +++ b/Charts/Classes/Filters/ChartDataApproximatorFilter.swift @@ -0,0 +1,216 @@ +// +// ChartDataApproximator.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartDataApproximatorFilter: ChartDataBaseFilter +{ + @objc + public enum ApproximatorType: Int + { + case None + case RamerDouglasPeucker + } + + /// the type of filtering algorithm to use + public var type = ApproximatorType.None + + /// the tolerance to be filtered with + /// When using the Douglas-Peucker-Algorithm, the tolerance is an angle in degrees, that will trigger the filtering + public var tolerance = Double(0.0) + + public var scaleRatio = Float(1.0) + public var deltaRatio = Float(1.0) + + public override init() + { + super.init(); + } + + /// Initializes the approximator with the given type and tolerance. + /// If toleranec <= 0, no filtering will be done. + public init(type: ApproximatorType, tolerance: Double) + { + super.init(); + + setup(type, tolerance: tolerance); + } + + /// Sets type and tolerance. + /// If tolerance <= 0, no filtering will be done. + public func setup(type: ApproximatorType, tolerance: Double) + { + self.type = type; + self.tolerance = tolerance; + } + + /// Sets the ratios for x- and y-axis, as well as the ratio of the scale levels + public func setRatios(deltaRatio: Float, scaleRatio: Float) + { + self.deltaRatio = deltaRatio; + self.scaleRatio = scaleRatio; + } + + /// Filters according to type. Uses the pre set set tolerance + /// + /// :param: points the points to filter + public override func filter(points: [ChartDataEntry]) -> [ChartDataEntry] + { + return filter(points, tolerance: tolerance); + } + + /// Filters according to type. + /// + /// :param: points the points to filter + /// :param: tolerance the angle in degrees that will trigger the filtering + public func filter(points: [ChartDataEntry], tolerance: Double) -> [ChartDataEntry] + { + if (tolerance <= 0) + { + return points; + } + + switch (type) + { + case .RamerDouglasPeucker: + return reduceWithDouglasPeuker(points, epsilon: tolerance); + case .None: + return points; + default: + return points; + } + } + + /// uses the douglas peuker algorithm to reduce the given arraylist of entries + private func reduceWithDouglasPeuker(entries: [ChartDataEntry], epsilon: Double) -> [ChartDataEntry] + { + // if a shape has 2 or less points it cannot be reduced + if (epsilon <= 0 || entries.count < 3) + { + return entries; + } + + var keep = [Bool](count: entries.count, repeatedValue: false); + + // first and last always stay + keep[0] = true; + keep[entries.count - 1] = true; + + // first and last entry are entry point to recursion + algorithmDouglasPeucker(entries, epsilon: epsilon, start: 0, end: entries.count - 1, keep: &keep); + + // create a new array with series, only take the kept ones + var reducedEntries = [ChartDataEntry](); + for (var i = 0; i < entries.count; i++) + { + if (keep[i]) + { + let curEntry = entries[i]; + reducedEntries.append(ChartDataEntry(value: curEntry.value, xIndex: curEntry.xIndex)); + } + } + + return reducedEntries; + } + + /// apply the Douglas-Peucker-Reduction to an ArrayList of Entry with a given epsilon (tolerance) + /// + /// :param: entries + /// :param: epsilon as y-value + /// :param: start + /// :param: end + private func algorithmDouglasPeucker(entries: [ChartDataEntry], epsilon: Double, start: Int, end: Int, inout keep: [Bool]) + { + if (end <= start + 1) + { + // recursion finished + return; + } + + // find the greatest distance between start and endpoint + var maxDistIndex = Int(0); + var distMax = Double(0.0); + + var firstEntry = entries[start]; + var lastEntry = entries[end]; + + for (var i = start + 1; i < end; i++) + { + var dist = calcAngleBetweenLines(firstEntry, end1: lastEntry, start2: firstEntry, end2: entries[i]); + + // keep the point with the greatest distance + if (dist > distMax) + { + distMax = dist; + maxDistIndex = i; + } + } + + if (distMax > epsilon) + { + // keep max dist point + keep[maxDistIndex] = true; + + // recursive call + algorithmDouglasPeucker(entries, epsilon: epsilon, start: start, end: maxDistIndex, keep: &keep); + algorithmDouglasPeucker(entries, epsilon: epsilon, start: maxDistIndex, end: end, keep: &keep); + } // else don't keep the point... + } + + /// calculate the distance between a line between two entries and an entry (point) + /// + /// :param: startEntry line startpoint + /// :param: endEntry line endpoint + /// :param: entryPoint the point to which the distance is measured from the line + private func calcPointToLineDistance(startEntry: ChartDataEntry, endEntry: ChartDataEntry, entryPoint: ChartDataEntry) -> Double + { + var xDiffEndStart = Float(endEntry.xIndex) - Float(startEntry.xIndex); + var xDiffEntryStart = Float(entryPoint.xIndex) - Float(startEntry.xIndex); + + var normalLength = sqrt((xDiffEndStart) + * (xDiffEndStart) + + (endEntry.value - startEntry.value) + * (endEntry.value - startEntry.value)); + + return Double(fabs((xDiffEntryStart) + * (endEntry.value - startEntry.value) + - (entryPoint.value - startEntry.value) + * (xDiffEndStart))) / Double(normalLength); + } + + /// Calculates the angle between two given lines. The provided entries mark the starting and end points of the lines. + private func calcAngleBetweenLines(start1: ChartDataEntry, end1: ChartDataEntry, start2: ChartDataEntry, end2: ChartDataEntry) -> Double + { + var angle1 = calcAngleWithRatios(start1, p2: end1); + var angle2 = calcAngleWithRatios(start2, p2: end2); + + return fabs(angle1 - angle2); + } + + /// calculates the angle between two entries (points) in the chart taking ratios into consideration + private func calcAngleWithRatios(p1: ChartDataEntry, p2: ChartDataEntry) -> Double + { + var dx = Double(p2.xIndex) * Double(deltaRatio) - Double(p1.xIndex) * Double(deltaRatio); + var dy = p2.value * scaleRatio - p1.value * scaleRatio; + return atan2(Double(dy), dx) * ChartUtils.Math.RAD2DEG; + } + + // calculates the angle between two entries (points) in the chart + private func calcAngle(p1: ChartDataEntry, p2: ChartDataEntry) -> Double + { + var dx = p2.xIndex - p1.xIndex; + var dy = p2.value - p1.value; + return atan2(Double(dy), Double(dx)) * ChartUtils.Math.RAD2DEG; + } +} \ No newline at end of file diff --git a/Charts/Classes/Filters/ChartDataBaseFilter.swift b/Charts/Classes/Filters/ChartDataBaseFilter.swift new file mode 100644 index 0000000000..b60561a5ae --- /dev/null +++ b/Charts/Classes/Filters/ChartDataBaseFilter.swift @@ -0,0 +1,28 @@ +// +// ChartDataFilter.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartDataBaseFilter: NSObject +{ + public override init() + { + super.init(); + } + + public func filter(points: [ChartDataEntry]) -> [ChartDataEntry] + { + fatalError("filter() cannot be called on ChartDataBaseFilter"); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/BarChartRenderer.swift b/Charts/Classes/Renderers/BarChartRenderer.swift new file mode 100644 index 0000000000..e85fd28616 --- /dev/null +++ b/Charts/Classes/Renderers/BarChartRenderer.swift @@ -0,0 +1,540 @@ +// +// BarChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +@objc +public protocol BarChartRendererDelegate +{ + func barChartRendererData(renderer: BarChartRenderer) -> BarChartData!; + func barChartRenderer(renderer: BarChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer!; + func barChartRendererMaxVisibleValueCount(renderer: BarChartRenderer) -> Int; + func barChartDefaultRendererValueFormatter(renderer: BarChartRenderer) -> NSNumberFormatter!; + func barChartRendererChartXMax(renderer: BarChartRenderer) -> Float; + func barChartIsDrawHighlightArrowEnabled(renderer: BarChartRenderer) -> Bool; + func barChartIsDrawValueAboveBarEnabled(renderer: BarChartRenderer) -> Bool; + func barChartIsDrawValuesForWholeStackEnabled(renderer: BarChartRenderer) -> Bool; + func barChartIsDrawBarShadowEnabled(renderer: BarChartRenderer) -> Bool; +} + +public class BarChartRenderer: ChartDataRendererBase +{ + public weak var delegate: BarChartRendererDelegate?; + + public init(delegate: BarChartRendererDelegate?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + self.delegate = delegate; + } + + public override func drawData(#context: CGContext) + { + var barData = delegate!.barChartRendererData(self); + + if (barData === nil) + { + return; + } + + for (var i = 0; i < barData.dataSetCount; i++) + { + var set = barData.getDataSetByIndex(i); + + if (set !== nil && set!.isVisible) + { + drawDataSet(context: context, dataSet: set as! BarChartDataSet, index: i); + } + } + } + + internal func drawDataSet(#context: CGContext, dataSet: BarChartDataSet, index: Int) + { + CGContextSaveGState(context); + + var barData = delegate!.barChartRendererData(self); + + var trans = delegate!.barChartRenderer(self, transformerForAxis: dataSet.axisDependency); + calcXBounds(trans); + + var drawBarShadowEnabled: Bool = delegate!.barChartIsDrawBarShadowEnabled(self); + var dataSetOffset = (barData.dataSetCount - 1); + var groupSpace = barData.groupSpace; + var groupSpaceHalf = groupSpace / 2.0; + var barSpace = dataSet.barSpace; + var barSpaceHalf = barSpace / 2.0; + var containsStacks = dataSet.isStacked; + var entries = dataSet.yVals as! [BarChartDataEntry]; + var barWidth: CGFloat = 0.5; + var phaseY = _animator.phaseY; + var barRect = CGRect(); + var barShadow = CGRect(); + var y: Float; + + // do the drawing + for (var j = 0, count = Int(ceil(CGFloat(dataSet.entryCount) * _animator.phaseX)); j < count; j++) + { + var e = entries[j]; + + // calculate the x-position, depending on datasetcount + var x = CGFloat(e.xIndex + j * dataSetOffset) + CGFloat(index) + + groupSpace * CGFloat(j) + groupSpaceHalf; + var vals = e.values; + + if (!containsStacks || vals == nil) + { + y = e.value; + + var left = x - barWidth + barSpaceHalf; + var right = x + barWidth - barSpaceHalf; + var top = y >= 0.0 ? CGFloat(y) : 0; + var bottom = y <= 0.0 ? CGFloat(y) : 0; + + // multiply the height of the rect with the phase + if (top > 0) + { + top *= phaseY; + } + else + { + bottom *= phaseY; + } + + barRect.origin.x = left; + barRect.size.width = right - left; + barRect.origin.y = top; + barRect.size.height = bottom - top; + + trans.rectValueToPixel(&barRect); + + if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width)) + { + continue; + } + + if (!viewPortHandler.isInBoundsRight(barRect.origin.x)) + { + break; + } + + // if drawing the bar shadow is enabled + if (drawBarShadowEnabled) + { + barShadow.origin.x = barRect.origin.x; + barShadow.origin.y = viewPortHandler.contentTop; + barShadow.size.width = barRect.size.width; + barShadow.size.height = viewPortHandler.contentHeight; + + CGContextSetFillColorWithColor(context, dataSet.barShadowColor.CGColor); + CGContextFillRect(context, barShadow); + } + + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + CGContextFillRect(context, barRect); + } + else + { + var all = e.value; + + // if drawing the bar shadow is enabled + if (drawBarShadowEnabled) + { + y = e.value; + + var left = x - barWidth + barSpaceHalf; + var right = x + barWidth - barSpaceHalf; + var top = y >= 0.0 ? CGFloat(y) : 0; + var bottom = y <= 0.0 ? CGFloat(y) : 0; + + // multiply the height of the rect with the phase + if (top > 0) + { + top *= phaseY; + } + else + { + bottom *= phaseY; + } + + barRect.origin.x = left; + barRect.size.width = right - left; + barRect.origin.y = top; + barRect.size.height = bottom - top; + + trans.rectValueToPixel(&barRect); + + barShadow.origin.x = barRect.origin.x; + barShadow.origin.y = viewPortHandler.contentTop; + barShadow.size.width = barRect.size.width; + barShadow.size.height = viewPortHandler.contentHeight; + + CGContextSetFillColorWithColor(context, dataSet.barShadowColor.CGColor); + CGContextFillRect(context, barShadow); + } + + // fill the stack + for (var k = 0; k < vals.count; k++) + { + all -= vals[k]; + y = vals[k] + all; + + var left = x - barWidth + barSpaceHalf; + var right = x + barWidth - barSpaceHalf; + var top = y >= 0.0 ? CGFloat(y) : 0; + var bottom = y <= 0.0 ? CGFloat(y) : 0; + + // multiply the height of the rect with the phase + if (top > 0) + { + top *= phaseY; + } + else + { + bottom *= phaseY; + } + + barRect.origin.x = left; + barRect.size.width = right - left; + barRect.origin.y = top; + barRect.size.height = bottom - top; + + trans.rectValueToPixel(&barRect); + + if (k == 0 && !viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width)) + { + // Skip to next bar + break; + } + + // avoid drawing outofbounds values + if (!viewPortHandler.isInBoundsRight(barRect.origin.x)) + { + break; + } + + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + CGContextSetFillColorWithColor(context, dataSet.colorAt(k).CGColor); + CGContextFillRect(context, barRect); + } + } + } + + CGContextRestoreGState(context); + } + + /// Prepares a bar for being highlighted. + internal func prepareBarHighlight(#x: CGFloat, y: Float, barspace: CGFloat, from: Float, trans: ChartTransformer, inout rect: CGRect) + { + var barWidth: CGFloat = 0.5; + + var spaceHalf = barspace / 2.0; + var left = x - barWidth + spaceHalf; + var right = x + barWidth - spaceHalf; + var top = y >= from ? CGFloat(y) : CGFloat(from); + var bottom = y <= from ? CGFloat(y) : CGFloat(from); + + rect.origin.x = left; + rect.origin.y = top; + rect.size.width = right - left; + rect.size.height = bottom - top; + + trans.rectValueToPixel(&rect, phaseY: _animator.phaseY); + } + + public override func drawValues(#context: CGContext) + { + // if values are drawn + if (passesCheck()) + { + var barData = delegate!.barChartRendererData(self); + + var defaultValueFormatter = delegate!.barChartDefaultRendererValueFormatter(self); + + var dataSets = barData.dataSets; + + var drawValueAboveBar = delegate!.barChartIsDrawValueAboveBarEnabled(self); + var drawValuesForWholeStackEnabled = delegate!.barChartIsDrawValuesForWholeStackEnabled(self); + + var valueTextHeight: CGFloat; + var posOffset: CGFloat; + var negOffset: CGFloat; + + for (var i = 0, count = barData.dataSetCount; i < count; i++) + { + var dataSet = dataSets[i]; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + // calculate the correct offset depending on the draw position of the value + let plus: CGFloat = 6.0; + var valueFont = dataSet.valueFont; + var valueTextHeight = valueFont.lineHeight; + var posOffset = (drawValueAboveBar ? -(valueTextHeight + plus) : plus); + var negOffset = (drawValueAboveBar ? plus : -(valueTextHeight + plus)); + + var valueTextColor = dataSet.valueTextColor; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + var trans = delegate!.barChartRenderer(self, transformerForAxis: dataSet.axisDependency); + + var entries = dataSet.yVals as! [BarChartDataEntry]; + + var valuePoints = getTransformedValues(trans: trans, entries: entries, dataSetIndex: i); + + // if only single values are drawn (sum) + if (!drawValuesForWholeStackEnabled) + { + for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) + { + if (!viewPortHandler.isInBoundsRight(valuePoints[j].x)) + { + break; + } + + if (!viewPortHandler.isInBoundsY(valuePoints[j].y) + || !viewPortHandler.isInBoundsLeft(valuePoints[j].x)) + { + continue; + } + + var val = entries[j].value; + + drawValue(context: context, + val: val, + xPos: valuePoints[j].x, + yPos: valuePoints[j].y + (val >= 0.0 ? posOffset : negOffset), + formatter: formatter!, + font: valueFont, + align: .Center, + color: valueTextColor); + } + } + else + { + // if each value of a potential stack should be drawn + + for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) + { + var e = entries[j]; + + var vals = e.values; + + // we still draw stacked bars, but there is one non-stacked in between + if (vals == nil) + { + if (!viewPortHandler.isInBoundsRight(valuePoints[j].x)) + { + break; + } + + if (!viewPortHandler.isInBoundsY(valuePoints[j].y) + || !viewPortHandler.isInBoundsLeft(valuePoints[j].x)) + { + continue; + } + + drawValue(context: context, + val: e.value, + xPos: valuePoints[j].x, + yPos: valuePoints[j].y + (e.value >= 0.0 ? posOffset : negOffset), + formatter: formatter!, + font: valueFont, + align: .Center, + color: valueTextColor); + } + else + { + var transformed = [CGPoint](); + var cnt = 0; + var add = e.value; + + for (var k = 0; k < vals.count; k++) + { + add -= vals[cnt]; + transformed.append(CGPoint(x: 0.0, y: (CGFloat(vals[cnt]) + CGFloat(add)) * _animator.phaseY)); + cnt++; + } + + trans.pointValuesToPixel(&transformed); + + for (var k = 0; k < transformed.count; k++) + { + var x = valuePoints[j].x; + var y = transformed[k].y + (vals[k] >= 0 ? posOffset : negOffset); + + if (!viewPortHandler.isInBoundsRight(x)) + { + break; + } + + if (!viewPortHandler.isInBoundsY(y) || !viewPortHandler.isInBoundsLeft(x)) + { + continue; + } + + drawValue(context: context, + val: vals[k], + xPos: x, + yPos: y, + formatter: formatter!, + font: valueFont, + align: .Center, + color: valueTextColor); + } + } + } + } + } + } + } + + /// Draws a value at the specified x and y position. + internal func drawValue(#context: CGContext, val: Float, xPos: CGFloat, yPos: CGFloat, formatter: NSNumberFormatter, font: UIFont, align: NSTextAlignment, color: UIColor) + { + var value = formatter.stringFromNumber(val)!; + ChartUtils.drawText(context: context, text: value, point: CGPoint(x: xPos, y: yPos), align: align, attributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: color]); + } + + public override func drawExtras(#context: CGContext) + { + + } + + private var _highlightArrowPtsBuffer = [CGPoint](count: 3, repeatedValue: CGPoint()); + + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + var barData = delegate!.barChartRendererData(self); + if (barData === nil) + { + return; + } + + CGContextSaveGState(context); + + var setCount = barData.dataSetCount; + var drawHighlightArrowEnabled = delegate!.barChartIsDrawHighlightArrowEnabled(self); + var barRect = CGRect(); + + for (var i = 0; i < indices.count; i++) + { + var h = indices[i]; + var index = h.xIndex; + + var dataSetIndex = h.dataSetIndex; + var set = barData.getDataSetByIndex(dataSetIndex) as! BarChartDataSet!; + + if (set === nil) + { + continue; + } + + var trans = delegate!.barChartRenderer(self, transformerForAxis: set.axisDependency); + + CGContextSetFillColorWithColor(context, set.highlightColor.CGColor); + CGContextSetAlpha(context, set.highLightAlpha); + + // check outofbounds + if (index < barData.yValCount && index >= 0 + && CGFloat(index) < (CGFloat(delegate!.barChartRendererChartXMax(self)) * _animator.phaseX) / CGFloat(setCount)) + { + var e = barData.getDataSetByIndex(dataSetIndex)!.entryForXIndex(index) as! BarChartDataEntry!; + + if (e === nil) + { + continue; + } + + var groupspace = barData.groupSpace; + var isStack = h.stackIndex < 0 ? false : true; + + // calculate the correct x-position + var x = CGFloat(index * setCount + dataSetIndex) + groupspace / 2.0 + groupspace * CGFloat(index); + var y = isStack ? e.values[h.stackIndex] + e.getBelowSum(h.stackIndex) : e.value; + + // this is where the bar starts + var from = isStack ? e.getBelowSum(h.stackIndex) : 0.0; + + prepareBarHighlight(x: x, y: y, barspace: set.barSpace, from: from, trans: trans, rect: &barRect); + + CGContextFillRect(context, barRect); + + if (drawHighlightArrowEnabled) + { + CGContextSetAlpha(context, 1.0); + + // distance between highlight arrow and bar + var offsetY = _animator.phaseY * 0.07; + + CGContextSaveGState(context); + + var pixelToValueMatrix = trans.pixelToValueMatrix; + var onePoint = CGPoint(x: 100.0, y: 100.0); + onePoint.x = onePoint.x * sqrt(pixelToValueMatrix.a * pixelToValueMatrix.a + pixelToValueMatrix.c * pixelToValueMatrix.c); + onePoint.y = onePoint.y * sqrt(pixelToValueMatrix.b * pixelToValueMatrix.b + pixelToValueMatrix.d * pixelToValueMatrix.d); + var xToYRel = abs(onePoint.y / onePoint.x); + + var arrowWidth = set.barSpace / 2.0; + var arrowHeight = arrowWidth * xToYRel; + + _highlightArrowPtsBuffer[0].x = CGFloat(x) + 0.4; + _highlightArrowPtsBuffer[0].y = CGFloat(y) + offsetY; + _highlightArrowPtsBuffer[1].x = CGFloat(x) + 0.4 + arrowWidth; + _highlightArrowPtsBuffer[1].y = CGFloat(y) + offsetY - arrowHeight; + _highlightArrowPtsBuffer[2].x = CGFloat(x) + 0.4 + arrowWidth; + _highlightArrowPtsBuffer[2].y = CGFloat(y) + offsetY + arrowHeight; + + trans.pointValuesToPixel(&_highlightArrowPtsBuffer); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, _highlightArrowPtsBuffer[0].x, _highlightArrowPtsBuffer[0].y); + CGContextAddLineToPoint(context, _highlightArrowPtsBuffer[1].x, _highlightArrowPtsBuffer[1].y); + CGContextAddLineToPoint(context, _highlightArrowPtsBuffer[2].x, _highlightArrowPtsBuffer[2].y); + CGContextClosePath(context); + + CGContextFillPath(context); + + CGContextRestoreGState(context); + } + } + } + + CGContextRestoreGState(context); + } + + public func getTransformedValues(#trans: ChartTransformer, entries: [BarChartDataEntry], dataSetIndex: Int) -> [CGPoint] + { + return trans.generateTransformedValuesBarChart(entries, dataSet: dataSetIndex, barData: delegate!.barChartRendererData(self)!, phaseY: _animator.phaseY); + } + + internal func passesCheck() -> Bool + { + var barData = delegate!.barChartRendererData(self); + + if (barData === nil) + { + return false; + } + + return CGFloat(barData.yValCount) < CGFloat(delegate!.barChartRendererMaxVisibleValueCount(self)) * viewPortHandler.scaleX; + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/CandleStickChartRenderer.swift b/Charts/Classes/Renderers/CandleStickChartRenderer.swift new file mode 100644 index 0000000000..6ff7c794d8 --- /dev/null +++ b/Charts/Classes/Renderers/CandleStickChartRenderer.swift @@ -0,0 +1,264 @@ +// +// CandleStickChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +@objc +public protocol CandleStickChartRendererDelegate +{ + func candleStickChartRendererCandleData(renderer: CandleStickChartRenderer) -> CandleChartData!; + func candleStickChartRenderer(renderer: CandleStickChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer!; + func candleStickChartDefaultRendererValueFormatter(renderer: CandleStickChartRenderer) -> NSNumberFormatter!; + func candleStickChartRendererChartYMax(renderer: CandleStickChartRenderer) -> Float; + func candleStickChartRendererChartYMin(renderer: CandleStickChartRenderer) -> Float; + func candleStickChartRendererChartXMax(renderer: CandleStickChartRenderer) -> Float; + func candleStickChartRendererChartXMin(renderer: CandleStickChartRenderer) -> Float; + func candleStickChartRendererMaxVisibleValueCount(renderer: CandleStickChartRenderer) -> Int; +} + +public class CandleStickChartRenderer: ChartDataRendererBase +{ + public weak var delegate: CandleStickChartRendererDelegate?; + + public init(delegate: CandleStickChartRendererDelegate?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + self.delegate = delegate; + } + + public override func drawData(#context: CGContext) + { + var candleData = delegate!.candleStickChartRendererCandleData(self); + + for set in candleData.dataSets as! [CandleChartDataSet] + { + if (set.isVisible) + { + drawDataSet(context: context, dataSet: set); + } + } + } + + private var _shadowPoints = [CGPoint](count: 2, repeatedValue: CGPoint()); + private var _bodyRect = CGRect(); + private var _lineSegments = [CGPoint](count: 2, repeatedValue: CGPoint()); + + internal func drawDataSet(#context: CGContext, dataSet: CandleChartDataSet) + { + var candleData = delegate!.candleStickChartRendererCandleData(self); + + var trans = delegate!.candleStickChartRenderer(self, transformerForAxis: dataSet.axisDependency); + calcXBounds(trans); + + var phaseX = _animator.phaseX; + var phaseY = _animator.phaseY; + var bodySpace = dataSet.bodySpace; + + var dataSetIndex = candleData.indexOfDataSet(dataSet); + + var entries = dataSet.yVals as! [CandleChartDataEntry]; + + CGContextSaveGState(context); + + CGContextSetLineWidth(context, dataSet.shadowWidth); + + for (var j = 0, count = Int(ceil(CGFloat(entries.count) * _animator.phaseX)); j < count; j++) + { + // get the color that is specified for this position from the DataSet, this will reuse colors, if the index is out of bounds + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + CGContextSetStrokeColorWithColor(context, dataSet.colorAt(j).CGColor); + + // get the entry + var e = entries[j]; + + if (e.xIndex < _minX || e.xIndex > _maxX) + { + continue; + } + + // calculate the shadow + + _shadowPoints[0].x = CGFloat(e.xIndex); + _shadowPoints[0].y = CGFloat(e.high) * phaseY; + _shadowPoints[1].x = CGFloat(e.xIndex); + _shadowPoints[1].y = CGFloat(e.low) * phaseY; + + trans.pointValuesToPixel(&_shadowPoints); + + // draw the shadow + + CGContextStrokeLineSegments(context, _shadowPoints, 2); + + // calculate the body + + _bodyRect.origin.x = CGFloat(e.xIndex) - 0.5 + bodySpace; + _bodyRect.origin.y = CGFloat(e.close) * phaseY; + _bodyRect.size.width = (CGFloat(e.xIndex) + 0.5 - bodySpace) - _bodyRect.origin.x; + _bodyRect.size.height = (CGFloat(e.open) * phaseY) - _bodyRect.origin.y; + + trans.rectValueToPixel(&_bodyRect); + + // decide whether the body is hollow or filled + if (_bodyRect.size.height > 0.0) + { + // draw the body + CGContextFillRect(context, _bodyRect); + } + else + { + // draw the body + CGContextStrokeRect(context, _bodyRect); + } + } + + CGContextRestoreGState(context); + } + + public override func drawValues(#context: CGContext) + { + var candleData = delegate!.candleStickChartRendererCandleData(self); + if (candleData === nil) + { + return; + } + + var defaultValueFormatter = delegate!.candleStickChartDefaultRendererValueFormatter(self); + + // if values are drawn + if (candleData.yValCount < Int(ceil(CGFloat(delegate!.candleStickChartRendererMaxVisibleValueCount(self)) * viewPortHandler.scaleX))) + { + var dataSets = candleData.dataSets; + + for (var i = 0; i < dataSets.count; i++) + { + var dataSet = dataSets[i]; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + var valueFont = dataSet.valueFont; + var valueTextColor = dataSet.valueTextColor; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + var trans = delegate!.candleStickChartRenderer(self, transformerForAxis: dataSet.axisDependency); + + var entries = dataSet.yVals as! [CandleChartDataEntry]; + + var positions = trans.generateTransformedValuesCandle(entries, phaseY: _animator.phaseY); + + var lineHeight = valueFont.lineHeight; + var yOffset: CGFloat = lineHeight + 5.0; + + for (var j = 0, count = Int(ceil(CGFloat(positions.count) * _animator.phaseX)); j < count; j++) + { + var x = positions[j].x; + var y = positions[j].y; + + if (!viewPortHandler.isInBoundsRight(x)) + { + break; + } + + if (!viewPortHandler.isInBoundsLeft(x) || !viewPortHandler.isInBoundsY(y)) + { + continue; + } + + var val = entries[j].high; + + ChartUtils.drawText(context: context, text: formatter!.stringFromNumber(val)!, point: CGPoint(x: x, y: y - yOffset), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + } + } + } + + public override func drawExtras(#context: CGContext) + { + } + + private var _vertPtsBuffer = [CGPoint](count: 4, repeatedValue: CGPoint()); + private var _horzPtsBuffer = [CGPoint](count: 4, repeatedValue: CGPoint()); + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + var candleData = delegate!.candleStickChartRendererCandleData(self); + if (candleData === nil) + { + return; + } + + for (var i = 0; i < indices.count; i++) + { + var xIndex = indices[i].xIndex; // get the x-position + + var set = candleData.getDataSetByIndex(indices[i].dataSetIndex) as! CandleChartDataSet!; + + if (set === nil) + { + continue; + } + + var e = set.entryForXIndex(xIndex) as! CandleChartDataEntry!; + + if (e === nil) + { + continue; + } + + var trans = delegate!.candleStickChartRenderer(self, transformerForAxis: set.axisDependency); + + CGContextSetStrokeColorWithColor(context, set.highlightColor.CGColor); + CGContextSetLineWidth(context, set.highlightLineWidth); + if (set.highlightLineDashLengths != nil) + { + CGContextSetLineDash(context, set.highlightLineDashPhase, set.highlightLineDashLengths!, set.highlightLineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var low = CGFloat(e.low) * _animator.phaseY; + var high = CGFloat(e.high) * _animator.phaseY; + + var min = delegate!.candleStickChartRendererChartYMin(self); + var max = delegate!.candleStickChartRendererChartYMax(self); + + _vertPtsBuffer[0] = CGPoint(x: CGFloat(xIndex) - 0.5, y: CGFloat(max)); + _vertPtsBuffer[1] = CGPoint(x: CGFloat(xIndex) - 0.5, y: CGFloat(min)); + _vertPtsBuffer[2] = CGPoint(x: CGFloat(xIndex) + 0.5, y: CGFloat(max)); + _vertPtsBuffer[3] = CGPoint(x: CGFloat(xIndex) + 0.5, y: CGFloat(min)); + + _horzPtsBuffer[0] = CGPoint(x: CGFloat(0.0), y: low); + _horzPtsBuffer[1] = CGPoint(x: CGFloat(delegate!.candleStickChartRendererChartXMax(self)), y: low); + _horzPtsBuffer[2] = CGPoint(x: 0.0, y: high); + _horzPtsBuffer[3] = CGPoint(x: CGFloat(delegate!.candleStickChartRendererChartXMax(self)), y: high); + + trans.pointValuesToPixel(&_vertPtsBuffer); + trans.pointValuesToPixel(&_horzPtsBuffer); + + // draw the vertical highlight lines + CGContextStrokeLineSegments(context, _vertPtsBuffer, 4); + + // draw the horizontal highlight lines + CGContextStrokeLineSegments(context, _horzPtsBuffer, 4); + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartAxisRendererBase.swift b/Charts/Classes/Renderers/ChartAxisRendererBase.swift new file mode 100644 index 0000000000..07544f6292 --- /dev/null +++ b/Charts/Classes/Renderers/ChartAxisRendererBase.swift @@ -0,0 +1,50 @@ +// +// ChartAxisRendererBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartAxisRendererBase: ChartRendererBase +{ + internal var transformer: ChartTransformer!; + + public override init() + { + super.init(); + } + + public init(viewPortHandler: ChartViewPortHandler, transformer: ChartTransformer!) + { + super.init(viewPortHandler: viewPortHandler); + + self.transformer = transformer; + } + + /// Draws the axis labels on the specified context + public func renderAxisLabels(#context: CGContext) + { + fatalError("renderAxisLabels() cannot be called on ChartAxisRendererBase"); + } + + /// Draws the grid lines belonging to the axis. + public func renderGridLines(#context: CGContext) + { + fatalError("renderGridLines() cannot be called on ChartAxisRendererBase"); + } + + /// Draws the line that goes alongside the axis. + internal func renderAxisLine(#context: CGContext) + { + fatalError("renderAxisLine() cannot be called on ChartAxisRendererBase"); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartDataRendererBase.swift b/Charts/Classes/Renderers/ChartDataRendererBase.swift new file mode 100644 index 0000000000..93d7a23a52 --- /dev/null +++ b/Charts/Classes/Renderers/ChartDataRendererBase.swift @@ -0,0 +1,45 @@ +// +// ChartDataRendererBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartDataRendererBase: ChartRendererBase +{ + internal var _animator: ChartAnimator!; + + public init(animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(viewPortHandler: viewPortHandler); + _animator = animator; + } + + public func drawData(#context: CGContext) + { + fatalError("drawData() cannot be called on ChartDataRendererBase"); + } + + public func drawValues(#context: CGContext) + { + fatalError("drawValues() cannot be called on ChartDataRendererBase"); + } + + public func drawExtras(#context: CGContext) + { + fatalError("drawExtras() cannot be called on ChartDataRendererBase"); + } + + public func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + fatalError("drawHighlighted() cannot be called on ChartDataRendererBase"); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartLegendRenderer.swift b/Charts/Classes/Renderers/ChartLegendRenderer.swift new file mode 100644 index 0000000000..ee8f8ce6d4 --- /dev/null +++ b/Charts/Classes/Renderers/ChartLegendRenderer.swift @@ -0,0 +1,432 @@ +// +// ChartLegendRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import CoreGraphics; + +public class ChartLegendRenderer: ChartRendererBase +{ + public override init(viewPortHandler: ChartViewPortHandler) + { + super.init(viewPortHandler: viewPortHandler); + } + + /// Prepares the legend and calculates all needed forms and colors. + public func computeLegend(data: ChartData, legend: ChartLegend!) -> ChartLegend + { + var labels = [String?](); + var colors = [UIColor?](); + + // loop for building up the colors and labels used in the legend + for (var i = 0, count = data.dataSetCount; i < count; i++) + { + var dataSet = data.getDataSetByIndex(i)!; + + var clrs: [UIColor] = dataSet.colors; + var entryCount = dataSet.entryCount; + + // if we have a barchart with stacked bars + if (dataSet.isKindOfClass(BarChartDataSet) && (dataSet as! BarChartDataSet).stackSize > 1) + { + var bds = dataSet as! BarChartDataSet; + var sLabels = bds.stackLabels; + + for (var j = 0; j < clrs.count && j < bds.stackSize; j++) + { + labels.append(sLabels[j % sLabels.count]); + colors.append(clrs[j]); + } + + // add the legend description label + colors.append(UIColor.clearColor()); + labels.append(bds.label); + + } + else if (dataSet.isKindOfClass(PieChartDataSet)) + { + var xVals = data.xVals; + var pds = dataSet as! PieChartDataSet; + + for (var j = 0; j < clrs.count && j < entryCount && j < xVals.count; j++) + { + labels.append(xVals[j]); + colors.append(clrs[j]); + } + + // add the legend description label + colors.append(UIColor.clearColor()); + labels.append(pds.label); + } + else + { // all others + + for (var j = 0; j < clrs.count && j < entryCount; j++) + { + // if multiple colors are set for a DataSet, group them + if (j < clrs.count - 1 && j < entryCount - 1) + { + labels.append(nil); + } + else + { // add label to the last entry + labels.append(dataSet.label); + } + + colors.append(clrs[j]); + } + } + } + + var l = ChartLegend(colors: colors, labels: labels); + + if (legend !== nil) + { + // apply the old legend settings to a potential new legend + l.apply(legend); + } + + // calculate all dimensions of the legend + l.calculateDimensions(l.font); + + return l; + } + + public func renderLegend(#context: CGContext, legend: ChartLegend!) + { + if (legend === nil || !legend.enabled) + { + return; + } + + var labelFont = legend.font; + var labelTextColor = legend.textColor; + var labelLineHeight = labelFont.lineHeight; + + var labels = legend.labels; + + var formSize = legend.formSize; + + // space between text and shape/form of entry + var formTextSpaceAndForm = legend.formToTextSpace + formSize; + + // space between the entries + var stackSpace = legend.stackSpace; + + // the amount of pixels the text needs to be set down to be on the same height as the form + var textDrop = (labelFont.lineHeight + formSize) / 2.0; + + // contains the stacked legend size in pixels + var stack = CGFloat(0.0); + + var wasStacked = false; + + var yoffset = legend.yOffset; + var xoffset = legend.xOffset; + + switch (legend.position) + { + case .BelowChartLeft: + + var posX = viewPortHandler.contentLeft + xoffset; + var posY = viewPortHandler.chartHeight - yoffset; + + for (var i = 0; i < labels.count; i++) + { + drawForm(context, x: posX, y: posY - legend.textHeightMax / 2.0, colorIndex: i, legend: legend); + + // grouped forms have null labels + if (labels[i] != nil) + { + // make a step to the left + if (legend.colors[i] != UIColor.clearColor()) + { + posX += formTextSpaceAndForm; + } + + drawLabel(context, x: posX, y: posY - legend.textHeightMax, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + posX += (labels[i] as NSString!).sizeWithAttributes([NSFontAttributeName: labelFont]).width + legend.xEntrySpace; + } + else + { + posX += formSize + stackSpace; + } + } + + break; + case .BelowChartRight: + + var posX = viewPortHandler.contentRight - xoffset; + var posY = viewPortHandler.chartHeight - yoffset; + + for (var i = labels.count - 1; i >= 0; i--) + { + if (labels[i] != nil) + { + posX -= (labels[i] as NSString!).sizeWithAttributes([NSFontAttributeName: labelFont]).width + legend.xEntrySpace; + drawLabel(context, x: posX, y: posY - legend.textHeightMax, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + if (legend.colors[i] != UIColor.clearColor()) + { + posX -= formTextSpaceAndForm; + } + } + else + { + posX -= stackSpace + formSize; + } + + drawForm(context, x: posX, y: posY - legend.textHeightMax / 2.0, colorIndex: i, legend: legend); + } + + break; + case .RightOfChart: + + var posX = viewPortHandler.chartWidth - legend.textWidthMax - xoffset; + var posY = viewPortHandler.contentTop + yoffset; + + for (var i = 0; i < labels.count; i++) + { + drawForm(context, x: posX + stack, y: posY, colorIndex: i, legend: legend); + + if (labels[i] != nil) + { + if (!wasStacked) + { + var x = posX; + + if (legend.colors[i] != UIColor.clearColor()) + { + x += formTextSpaceAndForm; + } + + drawLabel(context, x: x, y: posY - legend.textHeightMax / 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + + posY += textDrop; + } + else + { + posY += legend.textHeightMax * 3.0; + drawLabel(context, x: posX, y: posY - legend.textHeightMax * 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + } + + // make a step down + posY += legend.yEntrySpace; + stack = 0.0; + } + else + { + stack += formSize + stackSpace; + wasStacked = true; + } + } + break; + case .RightOfChartCenter: + var posX = viewPortHandler.chartWidth - legend.textWidthMax - xoffset; + var posY = viewPortHandler.chartHeight / 2.0 - legend.neededHeight / 2.0; + + for (var i = 0; i < labels.count; i++) + { + drawForm(context, x: posX + stack, y: posY, colorIndex: i, legend: legend); + + if (labels[i] != nil) + { + if (!wasStacked) + { + var x = posX; + + if (legend.colors[i] != UIColor.clearColor()) + { + x += formTextSpaceAndForm; + } + + drawLabel(context, x: x, y: posY - legend.textHeightMax / 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + + posY += textDrop; + } + else + { + posY += legend.textHeightMax * 3.0; + drawLabel(context, x: posX, y: posY - legend.textHeightMax * 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + } + + // make a step down + posY += legend.yEntrySpace; + stack = 0.0; + } + else + { + stack += formSize + stackSpace; + wasStacked = true; + } + } + + break; + case .BelowChartCenter: + + var posX = viewPortHandler.chartWidth / 2.0 - legend.neededWidth / 2.0; + var posY = viewPortHandler.chartHeight - yoffset; + + for (var i = 0; i < labels.count; i++) + { + drawForm(context, x: posX, y: posY - legend.textHeightMax / 2.0, colorIndex: i, legend: legend); + + // grouped forms have null labels + if (labels[i] != nil) + { + // make a step to the left + if (legend.colors[i] != -2) + { + posX += formTextSpaceAndForm; + } + + drawLabel(context, x: posX, y: posY - legend.textHeightMax, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + posX += (labels[i] as NSString!).sizeWithAttributes([NSFontAttributeName: labelFont]).width + legend.xEntrySpace; + } + else + { + posX += formSize + stackSpace; + } + } + + break; + case .PiechartCenter: + + var posX = viewPortHandler.chartWidth / 2.0 - legend.textWidthMax / 2.0; + var posY = viewPortHandler.chartHeight / 2.0 - legend.neededHeight / 2.0; + + for (var i = 0; i < labels.count; i++) + { + drawForm(context, x: posX + stack, y: posY, colorIndex: i, legend: legend); + + if (labels[i] != nil) + { + if (!wasStacked) + { + var x = posX; + + if (legend.colors[i] != UIColor.clearColor()) + { + x += formTextSpaceAndForm; + } + + drawLabel(context, x: x, y: posY - legend.textHeightMax / 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + + posY += textDrop; + } + else + { + posY += legend.textHeightMax * 3.0; + drawLabel(context, x: posX, y: posY - legend.textHeightMax * 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + } + + // make a step down + posY += legend.yEntrySpace; + stack = 0.0; + } + else + { + stack += formSize + stackSpace; + wasStacked = true; + } + } + + break; + case .RightOfChartInside: + var posX = viewPortHandler.chartWidth - legend.textWidthMax - xoffset; + var posY = viewPortHandler.contentTop + yoffset; + + for (var i = 0; i < labels.count; i++) + { + drawForm(context, x: posX + stack, y: posY, colorIndex: i, legend: legend); + + if (labels[i] != nil) + { + if (!wasStacked) + { + var x = posX; + + if (legend.colors[i] != UIColor.clearColor()) + { + x += formTextSpaceAndForm; + } + + drawLabel(context, x: x, y: posY - legend.textHeightMax / 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + + posY += textDrop; + } + else + { + posY += legend.textHeightMax * 3.0; + drawLabel(context, x: posX, y: posY - legend.textHeightMax * 2.0, label: legend.getLabel(i)!, font: labelFont, textColor: labelTextColor); + } + + // make a step down + posY += legend.yEntrySpace; + stack = 0.0; + } + else + { + stack += formSize + stackSpace; + wasStacked = true; + } + } + break; + } + } + + /// Draws the Legend-form at the given position with the color at the given index. + internal func drawForm(context: CGContext, x: CGFloat, y: CGFloat, colorIndex: Int, legend: ChartLegend) + { + var formColor = legend.colors[colorIndex]; + + if (formColor === nil || formColor == UIColor.clearColor()) + { + return; + } + + var formsize = legend.formSize; + + CGContextSaveGState(context); + + switch (legend.form) + { + case .Circle: + CGContextSetFillColorWithColor(context, formColor!.CGColor); + CGContextFillEllipseInRect(context, CGRect(x: x, y: y - formsize / 2.0, width: formsize, height: formsize)); + break; + case .Square: + CGContextSetFillColorWithColor(context, formColor!.CGColor); + CGContextFillRect(context, CGRect(x: x, y: y - formsize / 2.0, width: formsize, height: formsize)); + break; + case .Line: + + CGContextSetLineWidth(context, legend.formLineWidth); + CGContextSetStrokeColorWithColor(context, formColor!.CGColor); + + var lineSegments = UnsafeMutablePointer.alloc(2) + lineSegments[0] = CGPoint(x: x, y: y) + lineSegments[1] = CGPoint(x: x + formsize, y: y) + CGContextStrokeLineSegments(context, lineSegments, 2); + lineSegments.dealloc(2); + + break; + } + + CGContextRestoreGState(context); + } + + /// Draws the provided label at the given position. + internal func drawLabel(context: CGContext, x: CGFloat, y: CGFloat, label: String, font: UIFont, textColor: UIColor) + { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: x, y: y), align: .Left, attributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: textColor]); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartRendererBase.swift b/Charts/Classes/Renderers/ChartRendererBase.swift new file mode 100644 index 0000000000..3d56ccc165 --- /dev/null +++ b/Charts/Classes/Renderers/ChartRendererBase.swift @@ -0,0 +1,71 @@ +// +// ChartRendererBase.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartRendererBase: NSObject +{ + /// the component that handles the drawing area of the chart and it's offsets + public var viewPortHandler: ChartViewPortHandler!; + internal var _minX: Int = 0; + internal var _maxX: Int = 0; + + public override init() + { + super.init(); + } + + public init(viewPortHandler: ChartViewPortHandler) + { + super.init(); + self.viewPortHandler = viewPortHandler; + } + + /// Returns true if the specified value fits in between the provided min and max bounds, false if not. + internal func fitsBounds(val: Float, min: Float, max: Float) -> Bool + { + if (val < min || val > max) + { + return false; + } + else + { + return true; + } + } + + /// Calculates the minimum and maximum x-value the chart can currently display (with the given zoom level). + internal func calcXBounds(trans: ChartTransformer!) + { + var minx = trans.getValueByTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: 0.0)).x; + var maxx = trans.getValueByTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: 0.0)).x; + + if (isnan(minx)) + { + minx = 0; + } + if (isnan(maxx)) + { + maxx = 0; + } + + if (!isinf(minx)) + { + _minX = Int(minx); + } + if (!isinf(maxx)) + { + _maxX = Int(ceil(maxx)); + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartXAxisRenderer.swift b/Charts/Classes/Renderers/ChartXAxisRenderer.swift new file mode 100644 index 0000000000..e92dbd5e77 --- /dev/null +++ b/Charts/Classes/Renderers/ChartXAxisRenderer.swift @@ -0,0 +1,233 @@ +// +// ChartXAxisRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartXAxisRenderer: ChartAxisRendererBase +{ + internal var _xAxis: ChartXAxis!; + + public init(viewPortHandler: ChartViewPortHandler, xAxis: ChartXAxis, transformer: ChartTransformer!) + { + super.init(viewPortHandler: viewPortHandler, transformer: transformer); + + _xAxis = xAxis; + } + + public func computeAxis(#xValAverageLength: Float, xValues: [String]) + { + var a = ""; + + var max = Int(round(xValAverageLength + Float(_xAxis.spaceBetweenLabels))); + + for (var i = 0; i < max; i++) + { + a += "h"; + } + + var widthText = a as NSString; + var heightText = "Q" as NSString; + + _xAxis.labelWidth = widthText.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]).width; + _xAxis.labelHeight = _xAxis.labelFont.lineHeight; + _xAxis.values = xValues; + } + + public override func renderAxisLabels(#context: CGContext) + { + if (!_xAxis.isEnabled || !_xAxis.isDrawLabelsEnabled) + { + return; + } + + var yoffset = CGFloat(4.0); + + if (_xAxis.labelPosition == .Top) + { + drawLabels(context: context, pos: viewPortHandler.offsetTop - _xAxis.labelHeight - yoffset); + } + else if (_xAxis.labelPosition == .Bottom) + { + drawLabels(context: context, pos: viewPortHandler.contentBottom + yoffset * 1.5); + } + else if (_xAxis.labelPosition == .BottomInside) + { + drawLabels(context: context, pos: viewPortHandler.contentBottom - _xAxis.labelHeight - yoffset); + } + else if (_xAxis.labelPosition == .TopInside) + { + drawLabels(context: context, pos: viewPortHandler.offsetTop + yoffset); + } + else + { // BOTH SIDED + drawLabels(context: context, pos: viewPortHandler.offsetTop - _xAxis.labelHeight - yoffset); + drawLabels(context: context, pos: viewPortHandler.contentBottom + yoffset * 1.6); + } + } + + internal override func renderAxisLine(#context: CGContext) + { + if (!_xAxis.isEnabled || !_xAxis.isDrawAxisLineEnabled) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _xAxis.axisLineColor.CGColor); + CGContextSetLineWidth(context, _xAxis.axisLineWidth); + if (_xAxis.axisLineDashLengths != nil) + { + CGContextSetLineDash(context, _xAxis.axisLineDashPhase, _xAxis.axisLineDashLengths, _xAxis.axisLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var lineSegments = UnsafeMutablePointer.alloc(2) + + if (_xAxis.labelPosition == .Top + || _xAxis.labelPosition == .TopInside + || _xAxis.labelPosition == .BothSided) + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = viewPortHandler.contentTop; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + if (_xAxis.labelPosition == .Bottom + || _xAxis.labelPosition == .BottomInside + || _xAxis.labelPosition == .BothSided) + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = viewPortHandler.contentBottom; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } + + /// draws the x-labels on the specified y-position + internal func drawLabels(#context: CGContext, pos: CGFloat) + { + var labelFont = _xAxis.labelFont; + var labelTextColor = _xAxis.labelTextColor; + + var valueToPixelMatrix = transformer.valueToPixelMatrix; + + var position = CGPoint(x: 0.0, y: 0.0); + + var maxx = self._maxX; + var minx = self._minX; + + if (maxx >= _xAxis.values.count) + { + maxx = _xAxis.values.count - 1; + } + if (minx < 0) + { + minx = 0; + } + + for (var i = minx; i <= maxx; i += _xAxis.axisLabelModulus) + { + position.x = CGFloat(i); + position.y = 0.0; + position = CGPointApplyAffineTransform(position, valueToPixelMatrix); + + if (viewPortHandler.isInBoundsX(position.x)) + { + var label = _xAxis.values[i]; + var labelns = label as NSString; + + if (_xAxis.isAvoidFirstLastClippingEnabled) + { + // avoid clipping of the last + if (i == _xAxis.values.count - 1 && _xAxis.values.count > 1) + { + + var width = labelns.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]).width; + + if (width > viewPortHandler.offsetRight * 2.0 + && position.x + width > viewPortHandler.chartWidth) + { + position.x -= width / 2.0; + } + } + else if (i == 0) + { // avoid clipping of the first + var width = labelns.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]).width; + position.x += width / 2.0; + } + } + + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: position.x, y: pos), align: .Center, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } + } + + public override func renderGridLines(#context: CGContext) + { + if (!_xAxis.isDrawGridLinesEnabled || !_xAxis.isEnabled) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _xAxis.gridColor.CGColor); + CGContextSetLineWidth(context, _xAxis.gridLineWidth); + if (_xAxis.gridLineDashLengths != nil) + { + CGContextSetLineDash(context, _xAxis.gridLineDashPhase, _xAxis.gridLineDashLengths, _xAxis.gridLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var valueToPixelMatrix = transformer.valueToPixelMatrix; + + var position = CGPoint(x: 0.0, y: 0.0); + + var lineSegments = UnsafeMutablePointer.alloc(2) + + for (var i = _minX; i <= _maxX; i += _xAxis.axisLabelModulus) + { + position.x = CGFloat(i); + position.y = 0.0; + position = CGPointApplyAffineTransform(position, valueToPixelMatrix); + + if (position.x >= viewPortHandler.offsetLeft + && position.x <= viewPortHandler.chartWidth) + { + lineSegments[0].x = position.x; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = position.x; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartXAxisRendererBarChart.swift b/Charts/Classes/Renderers/ChartXAxisRendererBarChart.swift new file mode 100644 index 0000000000..67ee5173f2 --- /dev/null +++ b/Charts/Classes/Renderers/ChartXAxisRendererBarChart.swift @@ -0,0 +1,145 @@ +// +// ChartXAxisRendererBarChart.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartXAxisRendererBarChart: ChartXAxisRenderer +{ + internal weak var _chart: BarChartView!; + + public init(viewPortHandler: ChartViewPortHandler, xAxis: ChartXAxis, transformer: ChartTransformer!, chart: BarChartView) + { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: transformer); + + self._chart = chart; + } + + /// draws the x-labels on the specified y-position + internal override func drawLabels(#context: CGContext, pos: CGFloat) + { + if (_chart.data === nil) + { + return; + } + + var labelFont = _xAxis.labelFont; + var labelTextColor = _xAxis.labelTextColor; + + var barData = _chart.data as! BarChartData; + var step = barData.dataSetCount; + + var trans = transformer.valueToPixelMatrix; + + var position = CGPoint(x: 0.0, y: 0.0); + + var div = CGFloat(step) + (step > 1 ? barData.groupSpace : 0.0); + var min = Int(CGFloat(_minX) / div); + var max = Int(CGFloat(_maxX) / div); + + for (var i = min; i <= max; i += _xAxis.axisLabelModulus) + { + position.x = CGFloat(i * step) + CGFloat(i) * barData.groupSpace + barData.groupSpace / 2.0; + position.y = 0.0; + + // consider groups (center label for each group) + if (step > 1) + { + position.x += (CGFloat(step) - 1.0) / 2.0; + } + + position = CGPointApplyAffineTransform(position, trans); + + if (viewPortHandler.isInBoundsX(position.x) && i >= 0 && i < _xAxis.values.count) + { + var label = _xAxis.values[i]; + var labelns = label as NSString; + + if (_xAxis.isAvoidFirstLastClippingEnabled) + { + // avoid clipping of the last + if (i == _xAxis.values.count - 1) + { + var width = label.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]).width; + + if (width > viewPortHandler.offsetRight * 2.0 + && position.x + width > viewPortHandler.chartWidth) + { + position.x -= width / 2.0; + } + } + else if (i == 0) + { // avoid clipping of the first + var width = label.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]).width; + position.x += width / 2.0; + } + } + + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: position.x, y: pos), align: .Center, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } + } + + public override func renderGridLines(#context: CGContext) + { + if (!_xAxis.isDrawGridLinesEnabled || !_xAxis.isEnabled) + { + return; + } + + var barData = _chart.data as! BarChartData; + var step = barData.dataSetCount; + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _xAxis.gridColor.CGColor); + CGContextSetLineWidth(context, _xAxis.gridLineWidth); + if (_xAxis.gridLineDashLengths != nil) + { + CGContextSetLineDash(context, _xAxis.gridLineDashPhase, _xAxis.gridLineDashLengths, _xAxis.gridLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var trans = transformer.valueToPixelMatrix; + + var position = CGPoint(x: 0.0, y: 0.0); + + var lineSegments = UnsafeMutablePointer.alloc(2) + + var div = CGFloat(step) + (step > 1 ? barData.groupSpace : 0.0); + var min = Int(CGFloat(_minX) / div); + var max = Int(CGFloat(_maxX) / div); + + for (var i = min; i <= max; i += _xAxis.axisLabelModulus) + { + position.x = CGFloat(i * step) + CGFloat(i) * barData.groupSpace - 0.5; + position.y = 0.0; + position = CGPointApplyAffineTransform(position, trans); + + if (viewPortHandler.isInBoundsX(position.x)) + { + lineSegments[0].x = position.x; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = position.x; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartXAxisRendererHorizontalBarChart.swift b/Charts/Classes/Renderers/ChartXAxisRendererHorizontalBarChart.swift new file mode 100644 index 0000000000..d7e56f3b44 --- /dev/null +++ b/Charts/Classes/Renderers/ChartXAxisRendererHorizontalBarChart.swift @@ -0,0 +1,196 @@ +// +// ChartXAxisRendererHorizontalBarChart.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartXAxisRendererHorizontalBarChart: ChartXAxisRendererBarChart +{ + public override init(viewPortHandler: ChartViewPortHandler, xAxis: ChartXAxis, transformer: ChartTransformer!, chart: BarChartView) + { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: transformer, chart: chart); + } + + public override func computeAxis(#xValAverageLength: Float, xValues: [String]) + { + _xAxis.values = xValues; + + var longest = _xAxis.getLongestLabel() as NSString; + var longestSize = longest.sizeWithAttributes([NSFontAttributeName: _xAxis.labelFont]); + _xAxis.labelWidth = floor(longestSize.width + _xAxis.xOffset * 3.5); + _xAxis.labelHeight = longestSize.height; + } + + public override func renderAxisLabels(#context: CGContext) + { + if (!_xAxis.isEnabled || !_xAxis.isDrawLabelsEnabled || _chart.data === nil) + { + return; + } + + var xoffset = _xAxis.xOffset; + + if (_xAxis.labelPosition == .Top) + { + drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, align: .Left); + } + else if (_xAxis.labelPosition == .Bottom) + { + drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, align: .Right); + } + else if (_xAxis.labelPosition == .BottomInside) + { + drawLabels(context: context, pos: viewPortHandler.contentLeft + xoffset, align: .Left); + } + else if (_xAxis.labelPosition == .TopInside) + { + drawLabels(context: context, pos: viewPortHandler.contentRight - xoffset, align: .Right); + } + else + { // BOTH SIDED + drawLabels(context: context, pos: viewPortHandler.contentLeft, align: .Left); + drawLabels(context: context, pos: viewPortHandler.contentRight, align: .Left); + } + } + + /// draws the x-labels on the specified y-position + internal func drawLabels(#context: CGContext, pos: CGFloat, align: NSTextAlignment) + { + var labelFont = _xAxis.labelFont; + var labelTextColor = _xAxis.labelTextColor; + + // pre allocate to save performance (dont allocate in loop) + var position = CGPoint(x: 0.0, y: 0.0); + + var bd = _chart.data as! BarChartData; + var step = bd.dataSetCount; + + for (var i = 0; i < _xAxis.values.count; i += _xAxis.axisLabelModulus) + { + position.x = 0.0; + position.y = CGFloat(i * step) + CGFloat(i) * bd.groupSpace + bd.groupSpace / 2.0; + + // consider groups (center label for each group) + if (step > 1) + { + position.y += (CGFloat(step) - 1.0) / 2.0; + } + + transformer.pointValueToPixel(&position); + + if (viewPortHandler.isInBoundsY(position.y)) + { + var label = _xAxis.values[i]; + + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: pos, y: position.y - _xAxis.labelHeight / 2.0), align: align, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } + } + + public override func renderGridLines(#context: CGContext) + { + if (!_xAxis.isEnabled || !_xAxis.isDrawGridLinesEnabled || _chart.data === nil) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _xAxis.gridColor.CGColor); + CGContextSetLineWidth(context, _xAxis.gridLineWidth); + if (_xAxis.gridLineDashLengths != nil) + { + CGContextSetLineDash(context, _xAxis.gridLineDashPhase, _xAxis.gridLineDashLengths, _xAxis.gridLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var lineSegments = UnsafeMutablePointer.alloc(2) + + var position = CGPoint(x: 0.0, y: 0.0); + + var bd = _chart.data as! BarChartData; + + // take into consideration that multiple DataSets increase _deltaX + var step = bd.dataSetCount; + + for (var i = 0; i < _xAxis.values.count; i += _xAxis.axisLabelModulus) + { + position.x = 0.0; + position.y = CGFloat(i * step) + CGFloat(i) * bd.groupSpace - 0.5; + + transformer.pointValueToPixel(&position); + + if (viewPortHandler.isInBoundsY(position.y)) + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = position.y; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = position.y; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } + + internal override func renderAxisLine(#context: CGContext) + { + if (!_xAxis.isEnabled || !_xAxis.isDrawAxisLineEnabled) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _xAxis.axisLineColor.CGColor); + CGContextSetLineWidth(context, _xAxis.axisLineWidth); + if (_xAxis.axisLineDashLengths != nil) + { + CGContextSetLineDash(context, _xAxis.axisLineDashPhase, _xAxis.axisLineDashLengths, _xAxis.axisLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var lineSegments = UnsafeMutablePointer.alloc(2) + + if (_xAxis.labelPosition == .Top + || _xAxis.labelPosition == .TopInside + || _xAxis.labelPosition == .BothSided) + { + lineSegments[0].x = viewPortHandler.contentRight; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + if (_xAxis.labelPosition == .Bottom + || _xAxis.labelPosition == .BottomInside + || _xAxis.labelPosition == .BothSided) + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = viewPortHandler.contentLeft; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartXAxisRendererRadarChart.swift b/Charts/Classes/Renderers/ChartXAxisRendererRadarChart.swift new file mode 100644 index 0000000000..3e94a0adeb --- /dev/null +++ b/Charts/Classes/Renderers/ChartXAxisRendererRadarChart.swift @@ -0,0 +1,55 @@ +// +// ChartXAxisRendererRadarChart.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartXAxisRendererRadarChart: ChartXAxisRenderer +{ + private weak var _chart: RadarChartView!; + + public init(viewPortHandler: ChartViewPortHandler, xAxis: ChartXAxis, chart: RadarChartView) + { + super.init(viewPortHandler: viewPortHandler, xAxis: xAxis, transformer: nil); + + _chart = chart; + } + + public override func renderAxisLabels(#context: CGContext) + { + if (!_xAxis.isEnabled || !_xAxis.isDrawLabelsEnabled) + { + return; + } + + var labelFont = _xAxis.labelFont; + var labelTextColor = _xAxis.labelTextColor; + + var sliceangle = _chart.sliceAngle; + + // calculate the factor that is needed for transforming the value to pixels + var factor = _chart.factor; + + var center = _chart.centerOffsets; + + for (var i = 0, count = _xAxis.values.count; i < count; i++) + { + var text = _xAxis.values[i]; + + var angle = (sliceangle * CGFloat(i) + _chart.rotationAngle) % 360.0; + + var p = ChartUtils.getPosition(center: center, dist: CGFloat(_chart.yRange) * factor + _xAxis.labelWidth / 2.0, angle: angle); + + ChartUtils.drawText(context: context, text: text, point: CGPoint(x: p.x, y: p.y - _xAxis.labelHeight / 2.0), align: .Center, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartYAxisRenderer.swift b/Charts/Classes/Renderers/ChartYAxisRenderer.swift new file mode 100644 index 0000000000..b0e469b1ff --- /dev/null +++ b/Charts/Classes/Renderers/ChartYAxisRenderer.swift @@ -0,0 +1,355 @@ +// +// ChartYAxisRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartYAxisRenderer: ChartAxisRendererBase +{ + internal var _yAxis: ChartYAxis! + + public init(viewPortHandler: ChartViewPortHandler, yAxis: ChartYAxis, transformer: ChartTransformer!) + { + super.init(viewPortHandler: viewPortHandler, transformer: transformer); + + _yAxis = yAxis; + } + + /// Computes the axis values. + public func computeAxis(var #yMin: Float, var yMax: Float) + { + // calculate the starting and entry point of the y-labels (depending on + // zoom / contentrect bounds) + if (viewPortHandler.contentWidth > 10.0 && !viewPortHandler.isFullyZoomedOutY) + { + var p1 = transformer.getValueByTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)); + var p2 = transformer.getValueByTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)); + + if (!_yAxis.isInverted) + { + yMin = Float(p2.y); + yMax = Float(p1.y); + } + else + { + if (!_yAxis.isStartAtZeroEnabled) + { + yMin = Float(min(p1.y, p2.y)); + } + else + { + yMin = 0.0; + } + yMax = Float(max(p1.y, p2.y)); + } + } + + computeAxisValues(min: yMin, max: yMax); + } + + /// Sets up the y-axis labels. Computes the desired number of labels between + /// the two given extremes. Unlike the papareXLabels() method, this method + /// needs to be called upon every refresh of the view. + internal func computeAxisValues(#min: Float, max: Float) + { + var yMin = min; + var yMax = max; + + var labelCount = _yAxis.labelCount; + var range = abs(yMax - yMin); + + if (labelCount == 0 || range <= 0) + { + _yAxis.entries = [Float](); + return; + } + + var rawInterval = range / Float(labelCount); + var interval = ChartUtils.roundToNextSignificant(number: Double(rawInterval)); + var intervalMagnitude = pow(10.0, round(log10(interval))); + var intervalSigDigit = (interval / intervalMagnitude); + if (intervalSigDigit > 5) + { + // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 + interval = floor(10.0 * intervalMagnitude); + } + + // if the labels should only show min and max + if (_yAxis.isShowOnlyMinMaxEnabled) + { + _yAxis.entries = [yMin, yMax]; + } + else + { + var first = ceil(Double(yMin) / interval) * interval; + var last = ChartUtils.nextUp(floor(Double(yMax) / interval) * interval); + + var f: Double; + var i: Int; + var n = 0; + for (f = first; f <= last; f += interval) + { + ++n; + } + + if (_yAxis.entries.count < n) + { + // Ensure stops contains at least numStops elements. + _yAxis.entries = [Float](count: n, repeatedValue: 0.0); + } + else if (_yAxis.entries.count > n) + { + _yAxis.entries.removeRange(n..<_yAxis.entries.count); + } + + for (f = first, i = 0; i < n; f += interval, ++i) + { + _yAxis.entries[i] = Float(f); + } + } + } + + /// draws the y-axis labels to the screen + public override func renderAxisLabels(#context: CGContext) + { + if (!_yAxis.isEnabled || !_yAxis.isDrawLabelsEnabled) + { + return; + } + + var xoffset = _yAxis.xOffset; + var yoffset = _yAxis.labelFont.lineHeight / 2.5; + + var dependency = _yAxis.axisDependency; + var labelPosition = _yAxis.labelPosition; + + var xPos = CGFloat(0.0); + + var textAlign: NSTextAlignment; + + if (dependency == .Left) + { + if (labelPosition == .OutsideChart) + { + textAlign = .Right; + xPos = viewPortHandler.offsetLeft - xoffset; + } + else + { + textAlign = .Left; + xPos = viewPortHandler.offsetLeft + xoffset; + } + + } + else + { + if (labelPosition == .OutsideChart) + { + textAlign = .Left; + xPos = viewPortHandler.contentRight + xoffset; + } + else + { + textAlign = .Right; + xPos = viewPortHandler.contentRight - xoffset; + } + } + + drawYLabels(context: context, fixedPosition: xPos, offset: yoffset - _yAxis.labelFont.lineHeight, textAlign: textAlign); + } + + internal override func renderAxisLine(#context: CGContext) + { + if (!_yAxis.isEnabled || !_yAxis.drawAxisLineEnabled) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _yAxis.axisLineColor.CGColor); + CGContextSetLineWidth(context, _yAxis.axisLineWidth); + if (_yAxis.axisLineDashLengths != nil) + { + CGContextSetLineDash(context, _yAxis.axisLineDashPhase, _yAxis.axisLineDashLengths, _yAxis.axisLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var lineSegments = UnsafeMutablePointer.alloc(2) + + if (_yAxis.axisDependency == .Left) + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = viewPortHandler.contentLeft; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + else + { + lineSegments[0].x = viewPortHandler.contentRight; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } + + /// draws the y-labels on the specified x-position + internal func drawYLabels(#context: CGContext, fixedPosition: CGFloat, offset: CGFloat, textAlign: NSTextAlignment) + { + var labelFont = _yAxis.labelFont; + var labelTextColor = _yAxis.labelTextColor; + + var valueToPixelMatrix = transformer.valueToPixelMatrix; + + var pt = CGPoint(); + + for (var i = 0; i < _yAxis.entryCount; i++) + { + var text = _yAxis.getFormattedLabel(i); + + if (!_yAxis.isDrawTopYLabelEntryEnabled && i >= _yAxis.entryCount - 1) + { + break; + } + + pt.x = 0; + pt.y = CGFloat(_yAxis.entries[i]); + pt = CGPointApplyAffineTransform(pt, valueToPixelMatrix); + + pt.x = fixedPosition; + pt.y += offset; + + ChartUtils.drawText(context: context, text: text, point: pt, align: textAlign, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } + + private var _gridLineBuffer = [CGPoint](count: 2, repeatedValue: CGPoint()); + + public override func renderGridLines(#context: CGContext) + { + if (!_yAxis.isDrawGridLinesEnabled || !_yAxis.isEnabled) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _yAxis.gridColor.CGColor); + CGContextSetLineWidth(context, _yAxis.gridLineWidth); + if (_yAxis.gridLineDashLengths != nil) + { + CGContextSetLineDash(context, _yAxis.gridLineDashPhase, _yAxis.gridLineDashLengths, _yAxis.gridLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var valueToPixelMatrix = transformer.valueToPixelMatrix; + + var position = CGPoint(x: 0.0, y: 0.0); + + // draw the horizontal grid + for (var i = 0, count = _yAxis.entryCount; i < count; i++) + { + position.x = 0.0; + position.y = CGFloat(_yAxis.entries[i]); + position = CGPointApplyAffineTransform(position, valueToPixelMatrix) + + _gridLineBuffer[0].x = viewPortHandler.contentLeft; + _gridLineBuffer[0].y = position.y; + _gridLineBuffer[1].x = viewPortHandler.contentRight; + _gridLineBuffer[1].y = position.y; + CGContextStrokeLineSegments(context, _gridLineBuffer, 2); + } + + CGContextRestoreGState(context); + } + + /// Draws the LimitLines associated with this axis to the screen. + public func renderLimitLines(#context: CGContext) + { + var limitLines = _yAxis.limitLines; + + if (limitLines.count == 0) + { + return; + } + + CGContextSaveGState(context); + + var trans = transformer.valueToPixelMatrix; + + var position = CGPoint(x: 0.0, y: 0.0); + + var lineSegments = UnsafeMutablePointer.alloc(2) + + for (var i = 0; i < limitLines.count; i++) + { + var l = limitLines[i]; + + position.x = 0.0; + position.y = CGFloat(l.limit); + position = CGPointApplyAffineTransform(position, trans); + + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = position.y; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = position.y; + + CGContextSetStrokeColorWithColor(context, l.lineColor.CGColor); + CGContextSetLineWidth(context, l.lineWidth); + if (l.lineDashLengths != nil) + { + CGContextSetLineDash(context, l.lineDashPhase, l.lineDashLengths!, l.lineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + CGContextStrokeLineSegments(context, lineSegments, 2); + + var label = l.label; + + // if drawing the limit-value label is enabled + if (label.lengthOfBytesUsingEncoding(NSUTF16StringEncoding) > 0) + { + var xOffset = CGFloat(4.0); + var labelLineHeight = l.valueFont.lineHeight; + var yOffset = l.lineWidth + labelLineHeight / 2.0; + + if (l.labelPosition == .Right) + { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: viewPortHandler.contentRight - xOffset, y: position.y - yOffset - labelLineHeight), align: .Right, attributes: [NSFontAttributeName: l.valueFont, NSForegroundColorAttributeName: l.valueTextColor]); + } + else + { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: viewPortHandler.contentLeft + xOffset, y: position.y - yOffset - labelLineHeight), align: .Left, attributes: [NSFontAttributeName: l.valueFont, NSForegroundColorAttributeName: l.valueTextColor]); + } + } + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartYAxisRendererHorizontalBarChart.swift b/Charts/Classes/Renderers/ChartYAxisRendererHorizontalBarChart.swift new file mode 100644 index 0000000000..9822a5f400 --- /dev/null +++ b/Charts/Classes/Renderers/ChartYAxisRendererHorizontalBarChart.swift @@ -0,0 +1,278 @@ +// +// ChartYAxisRendererHorizontalBarChart.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartYAxisRendererHorizontalBarChart: ChartYAxisRenderer +{ + public override init(viewPortHandler: ChartViewPortHandler, yAxis: ChartYAxis, transformer: ChartTransformer!) + { + super.init(viewPortHandler: viewPortHandler, yAxis: yAxis, transformer: transformer); + } + + /// Computes the axis values. + public override func computeAxis(var #yMin: Float, var yMax: Float) + { + // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) + if (viewPortHandler.contentHeight > 10.0 && !viewPortHandler.isFullyZoomedOutX) + { + var p1 = transformer.getValueByTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)); + var p2 = transformer.getValueByTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)); + + if (!_yAxis.isInverted) + { + yMin = Float(p1.x); + yMax = Float(p2.x); + } + else + { + if (!_yAxis.isStartAtZeroEnabled) + { + yMin = Float(min(p1.x, p2.x)); + } + else + { + yMin = 0.0; + } + yMax = Float(max(p1.x, p2.x)); + } + } + + computeAxisValues(min: yMin, max: yMax); + } + + /// draws the y-axis labels to the screen + public override func renderAxisLabels(#context: CGContext) + { + if (!_yAxis.isEnabled || !_yAxis.isDrawLabelsEnabled) + { + return; + } + + var positions = [CGPoint](); + positions.reserveCapacity(_yAxis.entries.count); + + for (var i = 0; i < _yAxis.entries.count; i++) + { + positions.append(CGPoint(x: CGFloat(_yAxis.entries[i]), y: 0.0)) + } + + transformer.pointValuesToPixel(&positions); + + var lineHeight = _yAxis.labelFont.lineHeight; + var yoffset = lineHeight + _yAxis.yOffset; + + var dependency = _yAxis.axisDependency; + var labelPosition = _yAxis.labelPosition; + + var yPos: CGFloat = 0.0; + + if (dependency == .Left) + { + if (labelPosition == .OutsideChart) + { + yoffset = 3.0; + yPos = viewPortHandler.contentTop; + } + else + { + yoffset = yoffset * -1; + yPos = viewPortHandler.contentTop; + } + } + else + { + if (labelPosition == .OutsideChart) + { + yoffset = yoffset * -1.0; + yPos = viewPortHandler.contentBottom; + } + else + { + yoffset = 4.0; + yPos = viewPortHandler.contentBottom; + } + } + + yPos -= lineHeight; + + drawYLabels(context: context, fixedPosition: yPos, positions: positions, offset: yoffset); + } + + internal override func renderAxisLine(#context: CGContext) + { + if (!_yAxis.isEnabled || !_yAxis.drawAxisLineEnabled) + { + return; + } + + CGContextSaveGState(context); + + CGContextSetStrokeColorWithColor(context, _yAxis.axisLineColor.CGColor); + CGContextSetLineWidth(context, _yAxis.axisLineWidth); + if (_yAxis.axisLineDashLengths != nil) + { + CGContextSetLineDash(context, _yAxis.axisLineDashPhase, _yAxis.axisLineDashLengths, _yAxis.axisLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var lineSegments = UnsafeMutablePointer.alloc(2) + + if (_yAxis.axisDependency == .Left) + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = viewPortHandler.contentTop; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = viewPortHandler.contentTop; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + else + { + lineSegments[0].x = viewPortHandler.contentLeft; + lineSegments[0].y = viewPortHandler.contentBottom; + lineSegments[1].x = viewPortHandler.contentRight; + lineSegments[1].y = viewPortHandler.contentBottom; + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } + + /// draws the y-labels on the specified x-position + internal func drawYLabels(#context: CGContext, fixedPosition: CGFloat, positions: [CGPoint], offset: CGFloat) + { + var labelFont = _yAxis.labelFont; + var labelTextColor = _yAxis.labelTextColor; + + for (var i = 0; i < _yAxis.entryCount; i++) + { + var text = _yAxis.getFormattedLabel(i); + + if (!_yAxis.isDrawTopYLabelEntryEnabled && i >= _yAxis.entryCount - 1) + { + return; + } + + ChartUtils.drawText(context: context, text: text, point: CGPoint(x: positions[i].x, y: fixedPosition - offset), align: .Center, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } + + public override func renderGridLines(#context: CGContext) + { + if (!_yAxis.isEnabled || !_yAxis.isDrawGridLinesEnabled) + { + return; + } + + CGContextSaveGState(context); + + // pre alloc + var position = CGPoint(); + + CGContextSetStrokeColorWithColor(context, _yAxis.gridColor.CGColor); + CGContextSetLineWidth(context, _yAxis.gridLineWidth); + if (_yAxis.gridLineDashLengths != nil) + { + CGContextSetLineDash(context, _yAxis.gridLineDashPhase, _yAxis.gridLineDashLengths, _yAxis.gridLineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + // draw the horizontal grid + for (var i = 0; i < _yAxis.entryCount; i++) + { + position.x = CGFloat(_yAxis.entries[i]); + position.y = 0.0; + transformer.pointValueToPixel(&position); + + CGContextBeginPath(context); + CGContextMoveToPoint(context, position.x, viewPortHandler.contentTop); + CGContextAddLineToPoint(context, position.x, viewPortHandler.contentBottom); + CGContextStrokePath(context); + } + + CGContextRestoreGState(context); + } + + /// Draws the LimitLines associated with this axis to the screen. + public override func renderLimitLines(#context: CGContext) + { + var limitLines = _yAxis.limitLines; + + if (limitLines.count <= 0) + { + return; + } + + var pts = [CGPoint](count: 2, repeatedValue: CGPoint()); + var limitLinePath = CGPathCreateMutable(); + + CGContextSaveGState(context); + + for (var i = 0; i < limitLines.count; i++) + { + var l = limitLines[i]; + + pts[0].x = CGFloat(l.limit); + pts[1].x = CGFloat(l.limit); + + transformer.pointValuesToPixel(&pts); + + pts[0].y = viewPortHandler.contentTop; + pts[1].y = viewPortHandler.contentBottom; + + CGContextSetStrokeColorWithColor(context, l.lineColor.CGColor); + CGContextSetLineWidth(context, l.lineWidth); + if (l.lineDashLengths != nil) + { + CGContextSetLineDash(context, l.lineDashPhase, l.lineDashLengths!, l.lineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + CGContextStrokeLineSegments(context, pts, 2); + + var label = l.label; + + // if drawing the limit-value label is enabled + if (label.lengthOfBytesUsingEncoding(NSUTF16StringEncoding) > 0) + { + var xOffset = l.lineWidth; + var add: CGFloat = 4.0; + + var valueFont = l.valueFont; + var yOffset = valueFont.lineHeight + add / 2.0; + + if (l.labelPosition == .Right) + { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: pts[0].x + xOffset - valueFont.lineHeight, y: viewPortHandler.contentBottom - add), align: .Left, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: l.valueTextColor]); + } + else + { + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: pts[0].x + xOffset - valueFont.lineHeight, y: viewPortHandler.contentTop + yOffset), align: .Left, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: l.valueTextColor]); + } + } + } + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift b/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift new file mode 100644 index 0000000000..ae4ec2f73b --- /dev/null +++ b/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift @@ -0,0 +1,184 @@ +// +// ChartYAxisRendererRadarChart.swift +// Charts +// +// Created by Daniel Cohen Gindi on 3/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartYAxisRendererRadarChart: ChartYAxisRenderer +{ + private weak var _chart: RadarChartView!; + + public init(viewPortHandler: ChartViewPortHandler, yAxis: ChartYAxis, chart: RadarChartView) + { + super.init(viewPortHandler: viewPortHandler, yAxis: yAxis, transformer: nil); + + _chart = chart; + } + + public override func computeAxis(#yMin: Float, yMax: Float) + { + computeAxisValues(min: yMin, max: yMax); + } + + internal override func computeAxisValues(min yMin: Float, max yMax: Float) + { + var labelCount = _yAxis.labelCount; + var range = abs(yMax - yMin); + + if (labelCount == 0 || range <= 0) + { + _yAxis.entries = [Float](); + return; + } + + var rawInterval = range / Float(labelCount); + var interval = ChartUtils.roundToNextSignificant(number: Double(rawInterval)); + var intervalMagnitude = pow(10.0, round(log10(interval))); + var intervalSigDigit = Int(interval / intervalMagnitude); + + if (intervalSigDigit > 5) + { + // Use one order of magnitude higher, to avoid intervals like 0.9 or + // 90 + interval = floor(10 * intervalMagnitude); + } + + // if the labels should only show min and max + if (_yAxis.isShowOnlyMinMaxEnabled) + { + _yAxis.entries = [Float](); + _yAxis.entries.append(yMin); + _yAxis.entries.append(yMax); + } + else + { + var first = ceil(Double(yMin) / interval) * interval; + var last = ChartUtils.nextUp(floor(Double(yMax) / interval) * interval); + + var f: Double; + var i: Int; + var n = 0; + for (f = first; f <= last; f += interval) + { + ++n; + } + + if (isnan(_yAxis.customAxisMax)) + { + n += 1; + } + + if (_yAxis.entries.count < n) + { + // Ensure stops contains at least numStops elements. + _yAxis.entries = [Float](count: n, repeatedValue: 0.0); + } + + for (f = first, i = 0; i < n; f += interval, ++i) + { + _yAxis.entries[i] = Float(f); + } + } + + _yAxis.axisMaximum = _yAxis.entries[_yAxis.entryCount - 1]; + _yAxis.axisRange = abs(_yAxis.axisMaximum - _yAxis.axisMinimum); + } + + public override func renderAxisLabels(#context: CGContext) + { + if (!_yAxis.isEnabled || !_yAxis.isDrawLabelsEnabled) + { + return; + } + + var labelFont = _yAxis.labelFont; + var labelTextColor = _yAxis.labelTextColor; + + var center = _chart.centerOffsets; + var factor = _chart.factor; + + var labelCount = _yAxis.entryCount; + + var labelLineHeight = _yAxis.labelFont.lineHeight; + + for (var j = 0; j < labelCount; j++) + { + if (j == labelCount - 1 && _yAxis.isDrawTopYLabelEntryEnabled == false) + { + break; + } + + var r = CGFloat(_yAxis.entries[j] - _yAxis.axisMinimum) * factor; + + var p = ChartUtils.getPosition(center: center, dist: r, angle: _chart.rotationAngle); + + var label = _yAxis.getFormattedLabel(j); + + ChartUtils.drawText(context: context, text: label, point: CGPoint(x: p.x + 10.0, y: p.y - labelLineHeight), align: .Left, attributes: [NSFontAttributeName: labelFont, NSForegroundColorAttributeName: labelTextColor]); + } + } + + public override func renderLimitLines(#context: CGContext) + { + var limitLines = _yAxis.limitLines; + + if (limitLines.count == 0) + { + return; + } + + var sliceangle = _chart.sliceAngle; + + // calculate the factor that is needed for transforming the value to pixels + var factor = _chart.factor; + + var center = _chart.centerOffsets; + + for (var i = 0; i < limitLines.count; i++) + { + var l = limitLines[i]; + + CGContextSetStrokeColorWithColor(context, l.lineColor.CGColor); + CGContextSetLineWidth(context, l.lineWidth); + if (l.lineDashLengths != nil) + { + CGContextSetLineDash(context, l.lineDashPhase, l.lineDashLengths!, l.lineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var r = CGFloat(l.limit - _chart.chartYMin) * factor; + + CGContextBeginPath(context); + + for (var j = 0, count = _chart.data!.xValCount; j < count; j++) + { + var p = ChartUtils.getPosition(center: center, dist: r, angle: sliceangle * CGFloat(j) + _chart.rotationAngle); + + if (j == 0) + { + CGContextMoveToPoint(context, p.x, p.y); + } + else + { + CGContextAddLineToPoint(context, p.x, p.y); + } + } + + CGContextClosePath(context); + + CGContextStrokePath(context); + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/CombinedChartRenderer.swift b/Charts/Classes/Renderers/CombinedChartRenderer.swift new file mode 100644 index 0000000000..faa6172eae --- /dev/null +++ b/Charts/Classes/Renderers/CombinedChartRenderer.swift @@ -0,0 +1,341 @@ +// +// CombinedChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class CombinedChartRenderer: ChartDataRendererBase, + LineChartRendererDelegate, + BarChartRendererDelegate, + ScatterChartRendererDelegate, + CandleStickChartRendererDelegate +{ + private weak var _chart: CombinedChartView!; + + /// flag that enables or disables the highlighting arrow + public var drawHighlightArrowEnabled = false; + + /// if set to true, all values are drawn above their bars, instead of below their top + public var drawValueAboveBarEnabled = true; + + /// if set to true, all values of a stack are drawn individually, and not just their sum + public var drawValuesForWholeStackEnabled = true; + + /// if set to true, a grey area is darawn behind each bar that indicates the maximum value + public var drawBarShadowEnabled = true; + + internal var _renderers = [ChartDataRendererBase](); + + internal var _drawOrder: [CombinedChartView.DrawOrder] = [.Bar, .Line, .Candle, .Scatter]; + + public init(chart: CombinedChartView, animator: ChartAnimator, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + _chart = chart; + + createRenderers(); + } + + /// Creates the renderers needed for this combined-renderer in the required order. Also takes the DrawOrder into consideration. + internal func createRenderers() + { + _renderers = [ChartDataRendererBase](); + + for order in drawOrder + { + switch (order) + { + case .Bar: + if (_chart.barData !== nil) + { + _renderers.append(BarChartRenderer(delegate: self, animator: _animator, viewPortHandler: viewPortHandler)); + } + break; + case .Line: + if (_chart.lineData !== nil) + { + _renderers.append(LineChartRenderer(delegate: self, animator: _animator, viewPortHandler: viewPortHandler)); + } + break; + case .Candle: + if (_chart.candleData !== nil) + { + _renderers.append(CandleStickChartRenderer(delegate: self, animator: _animator, viewPortHandler: viewPortHandler)); + } + break; + case .Scatter: + if (_chart.scatterData !== nil) + { + _renderers.append(ScatterChartRenderer(delegate: self, animator: _animator, viewPortHandler: viewPortHandler)); + } + break; + } + } + + } + + public override func drawData(#context: CGContext) + { + for renderer in _renderers + { + renderer.drawData(context: context); + } + } + + public override func drawValues(#context: CGContext) + { + for renderer in _renderers + { + renderer.drawValues(context: context); + } + } + + public override func drawExtras(#context: CGContext) + { + for renderer in _renderers + { + renderer.drawExtras(context: context); + } + } + + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + for renderer in _renderers + { + renderer.drawHighlighted(context: context, indices: indices); + } + } + + /// Returns the sub-renderer object at the specified index. + public func getSubRenderer(#index: Int) -> ChartDataRendererBase! + { + if (index >= _renderers.count || index < 0) + { + return nil; + } + else + { + return _renderers[index]; + } + } + + // MARK: - LineChartRendererDelegate + + public func lineChartRendererData(renderer: LineChartRenderer) -> LineChartData! + { + return _chart.lineData; + } + + public func lineChartRenderer(renderer: LineChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return _chart.getTransformer(which); + } + + public func lineChartRendererFillFormatter(renderer: LineChartRenderer) -> ChartFillFormatter + { + return _chart.fillFormatter; + } + + public func lineChartDefaultRendererValueFormatter(renderer: LineChartRenderer) -> NSNumberFormatter! + { + return _chart._defaultValueFormatter; + } + + public func lineChartRendererChartYMax(renderer: LineChartRenderer) -> Float + { + return _chart.chartYMax; + } + + public func lineChartRendererChartYMin(renderer: LineChartRenderer) -> Float + { + return _chart.chartYMin; + } + + public func lineChartRendererChartXMax(renderer: LineChartRenderer) -> Float + { + return _chart.chartXMax; + } + + public func lineChartRendererChartXMin(renderer: LineChartRenderer) -> Float + { + return _chart.chartXMin; + } + + public func lineChartRendererMaxVisibleValueCount(renderer: LineChartRenderer) -> Int + { + return _chart.maxVisibleValueCount; + } + + // MARK: - BarChartRendererDelegate + + public func barChartRendererData(renderer: BarChartRenderer) -> BarChartData! + { + return _chart.barData; + } + + public func barChartRenderer(renderer: BarChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return _chart.getTransformer(which); + } + + public func barChartRendererMaxVisibleValueCount(renderer: BarChartRenderer) -> Int + { + return _chart.maxVisibleValueCount; + } + + public func barChartDefaultRendererValueFormatter(renderer: BarChartRenderer) -> NSNumberFormatter! + { + return _chart._defaultValueFormatter; + } + + public func barChartRendererChartXMax(renderer: BarChartRenderer) -> Float + { + return _chart.chartXMax; + } + + public func barChartIsDrawHighlightArrowEnabled(renderer: BarChartRenderer) -> Bool + { + return drawHighlightArrowEnabled; + } + + public func barChartIsDrawValueAboveBarEnabled(renderer: BarChartRenderer) -> Bool + { + return drawValueAboveBarEnabled; + } + + public func barChartIsDrawValuesForWholeStackEnabled(renderer: BarChartRenderer) -> Bool + { + return drawValuesForWholeStackEnabled; + } + + public func barChartIsDrawBarShadowEnabled(renderer: BarChartRenderer) -> Bool + { + return drawBarShadowEnabled; + } + + // MARK: - ScatterChartRendererDelegate + + public func scatterChartRendererData(renderer: ScatterChartRenderer) -> ScatterChartData! + { + return _chart.scatterData; + } + + public func scatterChartRenderer(renderer: ScatterChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return _chart.getTransformer(which); + } + + public func scatterChartDefaultRendererValueFormatter(renderer: ScatterChartRenderer) -> NSNumberFormatter! + { + return _chart._defaultValueFormatter; + } + + public func scatterChartRendererChartYMax(renderer: ScatterChartRenderer) -> Float + { + return _chart.chartYMax; + } + + public func scatterChartRendererChartYMin(renderer: ScatterChartRenderer) -> Float + { + return _chart.chartYMin; + } + + public func scatterChartRendererChartXMax(renderer: ScatterChartRenderer) -> Float + { + return _chart.chartXMax; + } + + public func scatterChartRendererChartXMin(renderer: ScatterChartRenderer) -> Float + { + return _chart.chartXMin; + } + + public func scatterChartRendererMaxVisibleValueCount(renderer: ScatterChartRenderer) -> Int + { + return _chart.maxVisibleValueCount; + } + + // MARK: - CandleStickChartRendererDelegate + + public func candleStickChartRendererCandleData(renderer: CandleStickChartRenderer) -> CandleChartData! + { + return _chart.candleData; + } + + public func candleStickChartRenderer(renderer: CandleStickChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer! + { + return _chart.getTransformer(which); + } + + public func candleStickChartDefaultRendererValueFormatter(renderer: CandleStickChartRenderer) -> NSNumberFormatter! + { + return _chart._defaultValueFormatter; + } + + public func candleStickChartRendererChartYMax(renderer: CandleStickChartRenderer) -> Float + { + return _chart.chartYMax; + } + + public func candleStickChartRendererChartYMin(renderer: CandleStickChartRenderer) -> Float + { + return _chart.chartYMin; + } + + public func candleStickChartRendererChartXMax(renderer: CandleStickChartRenderer) -> Float + { + return _chart.chartXMax; + } + + public func candleStickChartRendererChartXMin(renderer: CandleStickChartRenderer) -> Float + { + return _chart.chartXMin; + } + + public func candleStickChartRendererMaxVisibleValueCount(renderer: CandleStickChartRenderer) -> Int + { + return _chart.maxVisibleValueCount; + } + + // MARK: Accessors + + /// returns true if drawing the highlighting arrow is enabled, false if not + public var isDrawHighlightArrowEnabled: Bool { return drawHighlightArrowEnabled; } + + /// returns true if drawing values above bars is enabled, false if not + public var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled; } + + /// returns true if all values of a stack are drawn, and not just their sum + public var isDrawValuesForWholeStackEnabled: Bool { return drawValuesForWholeStackEnabled; } + + /// returns true if drawing shadows (maxvalue) for each bar is enabled, false if not + public var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled; } + + /// the order in which the provided data objects should be drawn. + /// The earlier you place them in the provided array, the further they will be in the background. + /// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines. + public var drawOrder: [CombinedChartView.DrawOrder] + { + get + { + return _drawOrder; + } + set + { + if (newValue.count > 0) + { + _drawOrder = newValue; + } + } + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift b/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift new file mode 100644 index 0000000000..8d88d0dac5 --- /dev/null +++ b/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift @@ -0,0 +1,404 @@ +// +// HorizontalBarChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class HorizontalBarChartRenderer: BarChartRenderer +{ + private var xOffset: CGFloat = 0.0; + private var yOffset: CGFloat = 0.0; + + public override init(delegate: BarChartRendererDelegate?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(delegate: delegate, animator: animator, viewPortHandler: viewPortHandler); + } + + internal override func drawDataSet(#context: CGContext, dataSet: BarChartDataSet, index: Int) + { + CGContextSaveGState(context); + + var barData = delegate!.barChartRendererData(self); + + var trans = delegate!.barChartRenderer(self, transformerForAxis: dataSet.axisDependency); + calcXBounds(trans); + + var drawBarShadowEnabled: Bool = delegate!.barChartIsDrawBarShadowEnabled(self); + var dataSetOffset = (barData.dataSetCount - 1); + var groupSpace = barData.groupSpace; + var groupSpaceHalf = groupSpace / 2.0; + var barSpace = dataSet.barSpace; + var barSpaceHalf = barSpace / 2.0; + var containsStacks = dataSet.isStacked; + var entries = dataSet.yVals as! [BarChartDataEntry]; + var barWidth: CGFloat = 0.5; + var phaseY = _animator.phaseY; + var barRect = CGRect(); + var barShadow = CGRect(); + var y: Float; + + // do the drawing + for (var j = 0, count = Int(ceil(CGFloat(dataSet.entryCount) * _animator.phaseX)); j < count; j++) + { + var e = entries[j]; + + // calculate the x-position, depending on datasetcount + var x = CGFloat(e.xIndex + j * dataSetOffset) + CGFloat(index) + + groupSpace * CGFloat(j) + groupSpaceHalf; + var vals = e.values; + + if (!containsStacks || vals == nil) + { + y = e.value; + + var bottom = x - barWidth + barSpaceHalf; + var top = x + barWidth - barSpaceHalf; + var right = y >= 0.0 ? CGFloat(y) : 0; + var left = y <= 0.0 ? CGFloat(y) : 0; + + // multiply the height of the rect with the phase + if (right > 0) + { + right *= phaseY; + } + else + { + left *= phaseY; + } + + barRect.origin.x = left; + barRect.size.width = right - left; + barRect.origin.y = top; + barRect.size.height = bottom - top; + + trans.rectValueToPixel(&barRect); + + if (!viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height)) + { + continue; + } + + if (!viewPortHandler.isInBoundsBottom(barRect.origin.y)) + { + break; + } + + // if drawing the bar shadow is enabled + if (drawBarShadowEnabled) + { + barShadow.origin.x = viewPortHandler.contentLeft; + barShadow.origin.y = barRect.origin.y; + barShadow.size.width = viewPortHandler.contentWidth; + barShadow.size.height = barRect.size.height; + + CGContextSetFillColorWithColor(context, dataSet.barShadowColor.CGColor); + CGContextFillRect(context, barShadow); + } + + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + CGContextFillRect(context, barRect); + } + else + { + var all = e.value; + + // if drawing the bar shadow is enabled + if (drawBarShadowEnabled) + { + y = e.value; + + var bottom = x - barWidth + barSpaceHalf; + var top = x + barWidth - barSpaceHalf; + var right = y >= 0.0 ? CGFloat(y) : 0.0; + var left = y <= 0.0 ? CGFloat(y) : 0.0; + + // multiply the height of the rect with the phase + if (right > 0) + { + right *= phaseY; + } + else + { + left *= phaseY; + } + + barRect.origin.x = left; + barRect.size.width = right - left; + barRect.origin.y = top; + barRect.size.height = bottom - top; + + trans.rectValueToPixel(&barRect); + + barShadow.origin.x = viewPortHandler.contentLeft; + barShadow.origin.y = barRect.origin.y; + barShadow.size.width = viewPortHandler.contentWidth; + barShadow.size.height = barRect.size.height; + + CGContextSetFillColorWithColor(context, dataSet.barShadowColor.CGColor); + CGContextFillRect(context, barShadow); + } + + // fill the stack + for (var k = 0; k < vals.count; k++) + { + all -= vals[k]; + y = vals[k] + all; + + var bottom = x - barWidth + barSpaceHalf; + var top = x + barWidth - barSpaceHalf; + var right = y >= 0.0 ? CGFloat(y) : 0.0; + var left = y <= 0.0 ? CGFloat(y) : 0.0; + + // multiply the height of the rect with the phase + if (right > 0) + { + right *= phaseY; + } + else + { + left *= phaseY; + } + + barRect.origin.x = left; + barRect.size.width = right - left; + barRect.origin.y = top; + barRect.size.height = bottom - top; + + trans.rectValueToPixel(&barRect); + + if (k == 0 && !viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height)) + { + // Skip to next bar + break; + } + + // avoid drawing outofbounds values + if (!viewPortHandler.isInBoundsBottom(barRect.origin.y)) + { + break; + } + + // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. + CGContextSetFillColorWithColor(context, dataSet.colorAt(k).CGColor); + CGContextFillRect(context, barRect); + } + } + } + + CGContextRestoreGState(context); + } + + internal override func prepareBarHighlight(#x: CGFloat, y: Float, barspace: CGFloat, from: Float, trans: ChartTransformer, inout rect: CGRect) + { + var barWidth: CGFloat = 0.5; + + var spaceHalf = barspace / 2.0; + var top = x - barWidth + spaceHalf; + var bottom = x + barWidth - spaceHalf; + var left = y >= from ? CGFloat(y) : CGFloat(from); + var right = y <= from ? CGFloat(y) : CGFloat(from); + + rect.origin.x = left; + rect.origin.y = top; + rect.size.width = right - left; + rect.size.height = bottom - top; + + trans.rectValueToPixel(&rect, phaseY: _animator.phaseY); + } + + public override func getTransformedValues(#trans: ChartTransformer, entries: [BarChartDataEntry], dataSetIndex: Int) -> [CGPoint] + { + return trans.generateTransformedValuesHorizontalBarChart(entries, dataSet: dataSetIndex, barData: delegate!.barChartRendererData(self)!, phaseY: _animator.phaseY); + } + + public override func drawValues(#context: CGContext) + { + // if values are drawn + if (passesCheck()) + { + var barData = delegate!.barChartRendererData(self); + + var defaultValueFormatter = delegate!.barChartDefaultRendererValueFormatter(self); + + var dataSets = barData.dataSets; + + var drawValueAboveBar = delegate!.barChartIsDrawValueAboveBarEnabled(self); + var drawValuesForWholeStackEnabled = delegate!.barChartIsDrawValuesForWholeStackEnabled(self); + + // calculate the correct offset depending on the draw position of the value + var negOffset: CGFloat = (drawValueAboveBar ? -5.0 : 5.0); + var posOffset: CGFloat = (drawValueAboveBar ? 5.0 : -5.0); + + var textAlign = drawValueAboveBar ? NSTextAlignment.Left : NSTextAlignment.Right; + + for (var i = 0, count = barData.dataSetCount; i < count; i++) + { + var dataSet = dataSets[i]; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + var valueFont = dataSet.valueFont; + var valueTextColor = dataSet.valueTextColor; + var yOffset = -valueFont.lineHeight / 2.0; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + var trans = delegate!.barChartRenderer(self, transformerForAxis: dataSet.axisDependency); + + var entries = dataSet.yVals as! [BarChartDataEntry]; + + var valuePoints = getTransformedValues(trans: trans, entries: entries, dataSetIndex: i); + + // if only single values are drawn (sum) + if (!drawValuesForWholeStackEnabled) + { + for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) + { + if (!viewPortHandler.isInBoundsX(valuePoints[j].x)) + { + continue; + } + + if (!viewPortHandler.isInBoundsTop(valuePoints[j].y)) + { + break; + } + + if (!viewPortHandler.isInBoundsBottom(valuePoints[j].y)) + { + continue; + } + + var val = entries[j].value; + + drawValue( + context: context, + val: val, + xPos: valuePoints[j].x + (val >= 0.0 ? posOffset : negOffset), + yPos: valuePoints[j].y + yOffset, + formatter: formatter!, + font: valueFont, + align: .Left, + color: valueTextColor); + } + } + else + { + // if each value of a potential stack should be drawn + + for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) + { + var e = entries[j]; + + var vals = e.values; + + // we still draw stacked bars, but there is one non-stacked in between + if (vals == nil) + { + if (!viewPortHandler.isInBoundsX(valuePoints[j].x)) + { + continue; + } + + if (!viewPortHandler.isInBoundsTop(valuePoints[j].y)) + { + break; + } + + if (!viewPortHandler.isInBoundsBottom(valuePoints[j].y)) + { + continue; + } + + var val = e.value; + + drawValue( + context: context, + val: val, + xPos: valuePoints[j].x + (val >= 0.0 ? posOffset : negOffset), + yPos: valuePoints[j].y + yOffset, + formatter: formatter!, + font: valueFont, + align: .Left, + color: valueTextColor); + } + else + { + var transformed = [CGPoint](); + var cnt = 0; + var add = e.value; + + for (var k = 0; k < vals.count; k++) + { + add -= vals[cnt]; + transformed.append(CGPoint(x: (CGFloat(vals[cnt]) + CGFloat(add)) * _animator.phaseY, y: 0.0)); + cnt++; + } + + trans.pointValuesToPixel(&transformed); + + for (var k = 0; k < transformed.count; k++) + { + var x = transformed[k].x + (vals[k] >= 0 ? posOffset : negOffset); + var y = valuePoints[j].y; + + if (!viewPortHandler.isInBoundsX(x)) + { + continue; + } + + if (!viewPortHandler.isInBoundsTop(y)) + { + break; + } + + if (!viewPortHandler.isInBoundsBottom(y)) + { + continue; + } + + drawValue(context: context, + val: vals[k], + xPos: x, + yPos: y, + formatter: formatter!, + font: valueFont, + align: .Left, + color: valueTextColor); + } + } + } + } + } + } + } + + internal override func passesCheck() -> Bool + { + var barData = delegate!.barChartRendererData(self); + + if (barData === nil) + { + return false; + } + + return CGFloat(barData.yValCount) < CGFloat(delegate!.barChartRendererMaxVisibleValueCount(self)) * viewPortHandler.scaleY; + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/LineChartRenderer.swift b/Charts/Classes/Renderers/LineChartRenderer.swift new file mode 100644 index 0000000000..deb546f36d --- /dev/null +++ b/Charts/Classes/Renderers/LineChartRenderer.swift @@ -0,0 +1,618 @@ +// +// LineChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +@objc +public protocol LineChartRendererDelegate +{ + func lineChartRendererData(renderer: LineChartRenderer) -> LineChartData!; + func lineChartRenderer(renderer: LineChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer!; + func lineChartRendererFillFormatter(renderer: LineChartRenderer) -> ChartFillFormatter; + func lineChartDefaultRendererValueFormatter(renderer: LineChartRenderer) -> NSNumberFormatter!; + func lineChartRendererChartYMax(renderer: LineChartRenderer) -> Float; + func lineChartRendererChartYMin(renderer: LineChartRenderer) -> Float; + func lineChartRendererChartXMax(renderer: LineChartRenderer) -> Float; + func lineChartRendererChartXMin(renderer: LineChartRenderer) -> Float; + func lineChartRendererMaxVisibleValueCount(renderer: LineChartRenderer) -> Int; +} + +public class LineChartRenderer: ChartDataRendererBase +{ + public weak var delegate: LineChartRendererDelegate?; + + public init(delegate: LineChartRendererDelegate?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + self.delegate = delegate; + } + + public override func drawData(#context: CGContext) + { + var lineData = delegate!.lineChartRendererData(self); + + if (lineData === nil) + { + return; + } + + for (var i = 0; i < lineData.dataSetCount; i++) + { + var set = lineData.getDataSetByIndex(i); + + if (set !== nil && set!.isVisible) + { + drawDataSet(context: context, dataSet: set as! LineChartDataSet); + } + } + } + + internal struct CGCPoint + { + internal var x: CGFloat = 0.0; + internal var y: CGFloat = 0.0; + + /// x-axis distance + internal var dx: CGFloat = 0.0; + /// y-axis distance + internal var dy: CGFloat = 0.0; + + internal init(x: CGFloat, y: CGFloat) + { + self.x = x; + self.y = y; + } + } + + internal func drawDataSet(#context: CGContext, dataSet: LineChartDataSet) + { + var lineData = delegate!.lineChartRendererData(self); + + var entries = dataSet.yVals; + + if (entries.count < 1) + { + return; + } + + calcXBounds(delegate!.lineChartRenderer(self, transformerForAxis: dataSet.axisDependency)); + + CGContextSaveGState(context); + + CGContextSetLineWidth(context, dataSet.lineWidth); + if (dataSet.lineDashLengths != nil) + { + CGContextSetLineDash(context, dataSet.lineDashPhase, dataSet.lineDashLengths, dataSet.lineDashLengths.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + // if drawing cubic lines is enabled + if (dataSet.isDrawCubicEnabled) + { + drawCubic(context: context, dataSet: dataSet, entries: entries); + } + else + { // draw normal (straight) lines + drawLinear(context: context, dataSet: dataSet, entries: entries); + } + + CGContextRestoreGState(context); + } + + internal func drawCubic(#context: CGContext, dataSet: LineChartDataSet, entries: [ChartDataEntry]) + { + var trans = delegate?.lineChartRenderer(self, transformerForAxis: dataSet.axisDependency); + + var minx = _minX; + var maxx = _maxX + 2; + + if (maxx > entries.count) + { + maxx = entries.count; + } + + var phaseX = _animator.phaseX; + var phaseY = _animator.phaseY; + + // get the color that is specified for this position from the DataSet + var drawingColor = dataSet.colors.first!; + + var intensity = dataSet.cubicIntensity; + + // the path for the cubic-spline + var cubicPath = CGPathCreateMutable(); + + var valueToPixelMatrix = trans!.valueToPixelMatrix; + + var size = Int(ceil(CGFloat(entries.count) * phaseX)); + + if (entries.count > 2) + { + var prevDx: CGFloat = 0.0; + var prevDy: CGFloat = 0.0; + var curDx: CGFloat = 0.0; + var curDy: CGFloat = 0.0; + + var cur = entries[0]; + var next = entries[1]; + var prev = entries[0]; + var prevPrev = entries[0]; + + // let the spline start + CGPathMoveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY); + + prevDx = CGFloat(next.xIndex - cur.xIndex) * intensity; + prevDy = CGFloat(next.value - cur.value) * intensity; + + cur = entries[1]; + next = entries[2]; + curDx = CGFloat(next.xIndex - prev.xIndex) * intensity; + curDy = CGFloat(next.value - prev.value) * intensity; + + // the first cubic + CGPathAddCurveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(prev.xIndex) + prevDx, (CGFloat(prev.value) + prevDy) * phaseY, CGFloat(cur.xIndex) - curDx, (CGFloat(cur.value) - curDy) * phaseY, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY); + + for (var j = 2; j < size - 1; j++) + { + prevPrev = entries[j - 2]; + prev = entries[j - 1]; + cur = entries[j]; + next = entries[j + 1]; + + prevDx = CGFloat(cur.xIndex - prevPrev.xIndex) * intensity; + prevDy = CGFloat(cur.value - prevPrev.value) * intensity; + curDx = CGFloat(next.xIndex - prev.xIndex) * intensity; + curDy = CGFloat(next.value - prev.value) * intensity; + + CGPathAddCurveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(prev.xIndex) + prevDx, (CGFloat(prev.value) + prevDy) * phaseY, + CGFloat(cur.xIndex) - curDx, + (CGFloat(cur.value) - curDy) * phaseY, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY); + } + + if (size > entries.count - 1) + { + cur = entries[entries.count - 1]; + prev = entries[entries.count - 2]; + prevPrev = entries[entries.count - 3]; + next = cur; + + prevDx = CGFloat(cur.xIndex - prevPrev.xIndex) * intensity; + prevDy = CGFloat(cur.value - prevPrev.value) * intensity; + curDx = CGFloat(next.xIndex - prev.xIndex) * intensity; + curDy = CGFloat(next.value - prev.value) * intensity; + + // the last cubic + CGPathAddCurveToPoint(cubicPath, &valueToPixelMatrix, CGFloat(prev.xIndex) + prevDx, (CGFloat(prev.value) + prevDy) * phaseY, + CGFloat(cur.xIndex) - curDx, + (CGFloat(cur.value) - curDy) * phaseY, CGFloat(cur.xIndex), CGFloat(cur.value) * phaseY); + } + } + + + CGContextSaveGState(context); + + if (dataSet.isDrawFilledEnabled) + { + drawCubicFill(context: context, dataSet: dataSet, spline: cubicPath, matrix: valueToPixelMatrix); + } + + CGContextBeginPath(context); + CGContextAddPath(context, cubicPath); + CGContextSetStrokeColorWithColor(context, drawingColor.CGColor); + CGContextStrokePath(context); + + CGContextRestoreGState(context); + } + + internal func drawCubicFill(#context: CGContext, dataSet: LineChartDataSet, spline: CGMutablePath, var matrix: CGAffineTransform) + { + CGContextSaveGState(context); + + var fillMin = delegate!.lineChartRendererFillFormatter(self).getFillLinePosition( + dataSet: dataSet, + data: delegate!.lineChartRendererData(self), + chartMaxY: delegate!.lineChartRendererChartYMax(self), + chartMinY: delegate!.lineChartRendererChartYMin(self)); + + var entryFrom = dataSet.entryForXIndex(_minX); + var entryTo = dataSet.entryForXIndex(_maxX + 1); + + var pt1 = CGPoint(x: CGFloat(entryTo.xIndex), y: fillMin); + var pt2 = CGPoint(x: CGFloat(entryFrom.xIndex), y: fillMin); + pt1 = CGPointApplyAffineTransform(pt1, matrix); + pt2 = CGPointApplyAffineTransform(pt2, matrix); + + CGContextBeginPath(context); + CGContextAddPath(context, spline); + CGContextAddLineToPoint(context, pt1.x, pt1.y); + CGContextAddLineToPoint(context, pt2.x, pt2.y); + CGContextClosePath(context); + + CGContextSetFillColorWithColor(context, dataSet.fillColor.CGColor); + CGContextSetAlpha(context, dataSet.fillAlpha); + CGContextFillPath(context); + + CGContextRestoreGState(context); + } + + private var _lineSegments = [CGPoint](count: 2, repeatedValue: CGPoint()); + + internal func drawLinear(#context: CGContext, dataSet: LineChartDataSet, entries: [ChartDataEntry]) + { + var lineData = delegate!.lineChartRendererData(self); + var dataSetIndex = lineData.indexOfDataSet(dataSet); + + var trans = delegate!.lineChartRenderer(self, transformerForAxis: dataSet.axisDependency); + var valueToPixelMatrix = trans.valueToPixelMatrix; + + var phaseX = _animator.phaseX; + var phaseY = _animator.phaseY; + + var pointBuffer = CGPoint(); + + CGContextSaveGState(context); + + // more than 1 color + if (dataSet.colors.count > 1) + { + for (var j = 0, count = Int(ceil(CGFloat(entries.count) * phaseX)); j < count; j++) + { + if (count > 1 && j == count - 1) + { // Last point, we have already drawn a line to this point + break; + } + + var e = entries[j]; + + _lineSegments[0].x = CGFloat(e.xIndex); + _lineSegments[0].y = CGFloat(e.value) * phaseY; + _lineSegments[0] = CGPointApplyAffineTransform(_lineSegments[0], valueToPixelMatrix); + if (j + 1 < count) + { + e = entries[j + 1]; + + _lineSegments[1].x = CGFloat(e.xIndex); + _lineSegments[1].y = CGFloat(e.value) * phaseY; + _lineSegments[1] = CGPointApplyAffineTransform(_lineSegments[1], valueToPixelMatrix); + } + else + { + _lineSegments[1] = _lineSegments[0]; + } + + if (!viewPortHandler.isInBoundsRight(_lineSegments[0].x)) + { + break; + } + + // make sure the lines don't do shitty things outside bounds + if (!viewPortHandler.isInBoundsLeft(_lineSegments[1].x) + || (!viewPortHandler.isInBoundsTop(_lineSegments[0].y) && !viewPortHandler.isInBoundsBottom(_lineSegments[1].y)) + || (!viewPortHandler.isInBoundsTop(_lineSegments[0].y) && !viewPortHandler.isInBoundsBottom(_lineSegments[1].y))) + { + continue; + } + + // get the color that is set for this line-segment + CGContextSetStrokeColorWithColor(context, dataSet.colorAt(j).CGColor); + CGContextStrokeLineSegments(context, _lineSegments, 2); + } + } + else + { // only one color per dataset + + var entryFrom = dataSet.entryForXIndex(_minX); + var entryTo = dataSet.entryForXIndex(_maxX); + + var minx = dataSet.entryIndex(entry: entryFrom, isEqual: true); + var maxx = dataSet.entryIndex(entry: entryTo, isEqual: true); + + var point = CGPoint(); + point.x = CGFloat(entries[minx].xIndex); + point.y = CGFloat(entries[minx].value) * phaseY; + point = CGPointApplyAffineTransform(point, valueToPixelMatrix) + + CGContextBeginPath(context); + CGContextMoveToPoint(context, point.x, point.y); + + // create a new path + for (var x = minx + 1, count = Int(ceil(CGFloat(min(entries.count, maxx) + 1) * phaseX)); x < count; x++) + { + var e = entries[x]; + + point.x = CGFloat(e.xIndex); + point.y = CGFloat(e.value) * phaseY; + point = CGPointApplyAffineTransform(point, valueToPixelMatrix) + + CGContextAddLineToPoint(context, point.x, point.y); + } + + CGContextSetStrokeColorWithColor(context, dataSet.colorAt(0).CGColor); + CGContextStrokePath(context); + } + + CGContextRestoreGState(context); + + // if drawing filled is enabled + if (dataSet.isDrawFilledEnabled && entries.count > 0) + { + drawLinearFill(context: context, dataSet: dataSet, entries: entries, trans: trans); + } + } + + internal func drawLinearFill(#context: CGContext, dataSet: LineChartDataSet, entries: [ChartDataEntry], trans: ChartTransformer) + { + var entryFrom = dataSet.entryForXIndex(_minX - 2); + var entryTo = dataSet.entryForXIndex(_maxX + 2); + + var minx = dataSet.entryIndex(entry: entryFrom, isEqual: true); + var maxx = dataSet.entryIndex(entry: entryTo, isEqual: true); + + CGContextSaveGState(context); + + CGContextSetFillColorWithColor(context, dataSet.fillColor.CGColor); + + // filled is usually drawn with less alpha + CGContextSetAlpha(context, dataSet.fillAlpha); + + var filled = generateFilledPath( + entries, + fillMin: delegate!.lineChartRendererFillFormatter(self).getFillLinePosition( + dataSet: dataSet, + data: delegate!.lineChartRendererData(self), + chartMaxY: delegate!.lineChartRendererChartYMax(self), + chartMinY: delegate!.lineChartRendererChartYMin(self)), + from: minx, + to: maxx, + matrix: trans.valueToPixelMatrix); + + CGContextBeginPath(context); + CGContextAddPath(context, filled); + CGContextFillPath(context); + + CGContextRestoreGState(context); + } + + /// Generates the path that is used for filled drawing. + private func generateFilledPath(entries: [ChartDataEntry], fillMin: CGFloat, from: Int, to: Int, var matrix: CGAffineTransform) -> CGPath + { + var point = CGPoint(); + + var phaseX = _animator.phaseX; + var phaseY = _animator.phaseY; + + var filled = CGPathCreateMutable(); + CGPathMoveToPoint(filled, &matrix, CGFloat(entries[from].xIndex), CGFloat(entries[from].value) * phaseY); + + // create a new path + for (var x = from + 1, count = Int(ceil(CGFloat(to) * phaseX)); x <= count; x++) + { + var e = entries[x]; + CGPathAddLineToPoint(filled, &matrix, CGFloat(e.xIndex), CGFloat(e.value) * phaseY); + } + + // close up + CGPathAddLineToPoint(filled, &matrix, CGFloat(entries[Int(CGFloat(to) * phaseX)].xIndex), fillMin); + CGPathAddLineToPoint(filled, &matrix, CGFloat(entries[from].xIndex), fillMin); + CGPathCloseSubpath(filled); + + return filled; + } + + public override func drawValues(#context: CGContext) + { + var lineData = delegate!.lineChartRendererData(self); + if (lineData === nil) + { + return; + } + + var defaultValueFormatter = delegate!.lineChartDefaultRendererValueFormatter(self); + + if (CGFloat(lineData.yValCount) < CGFloat(delegate!.lineChartRendererMaxVisibleValueCount(self)) * viewPortHandler.scaleX) + { + var dataSets = lineData.dataSets; + + for (var i = 0; i < dataSets.count; i++) + { + var dataSet = dataSets[i] as! LineChartDataSet; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + var valueFont = dataSet.valueFont; + var valueTextColor = dataSet.valueTextColor; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + var trans = delegate!.lineChartRenderer(self, transformerForAxis: dataSet.axisDependency); + + // make sure the values do not interfear with the circles + var valOffset = Int(dataSet.circleRadius * 1.75); + + if (!dataSet.isDrawCirclesEnabled) + { + valOffset = valOffset / 2; + } + + var entries = dataSet.yVals; + + var positions = trans.generateTransformedValuesLine(entries, phaseY: _animator.phaseY); + + for (var j = 0, count = Int(ceil(CGFloat(positions.count) * _animator.phaseX)); j < count; j++) + { + if (!viewPortHandler.isInBoundsRight(positions[j].x)) + { + break; + } + + if (!viewPortHandler.isInBoundsLeft(positions[j].x) || !viewPortHandler.isInBoundsY(positions[j].y)) + { + continue; + } + + var val = entries[j].value; + + ChartUtils.drawText(context: context, text: formatter!.stringFromNumber(val)!, point: CGPoint(x: positions[j].x, y: positions[j].y - CGFloat(valOffset) - valueFont.lineHeight), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + } + } + } + + public override func drawExtras(#context: CGContext) + { + drawCircles(context: context); + } + + private func drawCircles(#context: CGContext) + { + var phaseX = _animator.phaseX; + var phaseY = _animator.phaseY; + + var lineData = delegate!.lineChartRendererData(self); + + var dataSets = lineData.dataSets; + + var pt = CGPoint(); + var rect = CGRect(); + + CGContextSaveGState(context); + + for (var i = 0, count = dataSets.count; i < count; i++) + { + var dataSet = lineData.getDataSetByIndex(i) as! LineChartDataSet!; + + if (!dataSet.isVisible || !dataSet.isDrawCirclesEnabled) + { + continue; + } + + var trans = delegate!.lineChartRenderer(self, transformerForAxis: dataSet.axisDependency); + var valueToPixelMatrix = trans.valueToPixelMatrix; + + var entries = dataSet.yVals; + + var circleRadius = dataSet.circleRadius; + var circleDiameter = circleRadius * 2.0; + var circleHoleDiameter = circleRadius; + var circleHoleRadius = circleHoleDiameter / 2.0; + var isDrawCircleHoleEnabled = dataSet.isDrawCircleHoleEnabled; + + for (var j = 0, count = Int(ceil(CGFloat(entries.count) * _animator.phaseX)); j < count; j++) + { + var e = entries[j]; + pt.x = CGFloat(e.xIndex); + pt.y = CGFloat(e.value) * phaseY; + pt = CGPointApplyAffineTransform(pt, valueToPixelMatrix); + + if (!viewPortHandler.isInBoundsRight(pt.x)) + { + break; + } + + // make sure the circles don't do shitty things outside bounds + if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) + { + continue; + } + + CGContextSetFillColorWithColor(context, dataSet.getCircleColor(j)!.CGColor); + + rect.origin.x = pt.x - circleRadius; + rect.origin.y = pt.y - circleRadius; + rect.size.width = circleDiameter; + rect.size.height = circleDiameter; + CGContextFillEllipseInRect(context, rect); + + if (isDrawCircleHoleEnabled) + { + CGContextSetFillColorWithColor(context, dataSet.circleHoleColor.CGColor); + + rect.origin.x = pt.x - circleHoleRadius; + rect.origin.y = pt.y - circleHoleRadius; + rect.size.width = circleHoleDiameter; + rect.size.height = circleHoleDiameter; + CGContextFillEllipseInRect(context, rect); + } + } + } + + CGContextRestoreGState(context); + } + + var _highlightPtsBuffer = [CGPoint](count: 4, repeatedValue: CGPoint()); + + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + var lineData = delegate!.lineChartRendererData(self); + var chartXMax = delegate!.lineChartRendererChartXMax(self); + var chartYMax = delegate!.lineChartRendererChartYMax(self); + var chartYMin = delegate!.lineChartRendererChartYMin(self); + + CGContextSaveGState(context); + + for (var i = 0; i < indices.count; i++) + { + var set = lineData.getDataSetByIndex(indices[i].dataSetIndex) as! LineChartDataSet!; + + if (set === nil) + { + continue; + } + + CGContextSetStrokeColorWithColor(context, set.highlightColor.CGColor); + CGContextSetLineWidth(context, set.highlightLineWidth); + if (set.highlightLineDashLengths != nil) + { + CGContextSetLineDash(context, set.highlightLineDashPhase, set.highlightLineDashLengths!, set.highlightLineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var xIndex = indices[i].xIndex; // get the x-position + + if (CGFloat(xIndex) > CGFloat(chartXMax) * _animator.phaseX) + { + continue; + } + + var y = CGFloat(set.yValForXIndex(xIndex)) * _animator.phaseY; // get the y-position + + _highlightPtsBuffer[0] = CGPoint(x: CGFloat(xIndex), y: CGFloat(chartYMax)); + _highlightPtsBuffer[1] = CGPoint(x: CGFloat(xIndex), y: CGFloat(chartYMin)); + _highlightPtsBuffer[2] = CGPoint(x: 0.0, y: y); + _highlightPtsBuffer[3] = CGPoint(x: CGFloat(chartXMax), y: y); + + var trans = delegate!.lineChartRenderer(self, transformerForAxis: set.axisDependency); + + trans.pointValuesToPixel(&_highlightPtsBuffer); + + // draw the highlight lines + CGContextStrokeLineSegments(context, _highlightPtsBuffer, 4); + } + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/PieChartRenderer.swift b/Charts/Classes/Renderers/PieChartRenderer.swift new file mode 100644 index 0000000000..6d53d546dc --- /dev/null +++ b/Charts/Classes/Renderers/PieChartRenderer.swift @@ -0,0 +1,367 @@ +// +// PieChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class PieChartRenderer: ChartDataRendererBase +{ + internal weak var _chart: PieChartView! + + public var drawHoleEnabled = true + public var holeTransparent = true + public var holeColor: UIColor? = UIColor.whiteColor() + public var holeRadiusPercent = CGFloat(0.5) + public var transparentCircleRadiusPercent = CGFloat(0.55) + public var centerTextColor = UIColor.blackColor() + public var centerTextFont = UIFont.systemFontOfSize(12.0) + public var drawXLabelsEnabled = true + public var usePercentValuesEnabled = false + public var centerText: String! + public var drawCenterTextEnabled = true + + public init(chart: PieChartView, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + _chart = chart; + } + + public override func drawData(#context: CGContext) + { + if (_chart !== nil) + { + var pieData = _chart.data; + + if (pieData != nil) + { + for set in pieData!.dataSets as! [PieChartDataSet] + { + if (set.isVisible) + { + drawDataSet(context: context, dataSet: set); + } + } + } + } + } + + internal func drawDataSet(#context: CGContext, dataSet: PieChartDataSet) + { + var angle = _chart.rotationAngle; + + var cnt = 0; + + var entries = dataSet.yVals; + var drawAngles = _chart.drawAngles; + var circleBox = _chart.circleBox; + var radius = _chart.radius; + var innerRadius = drawHoleEnabled && holeTransparent ? radius * holeRadiusPercent : 0.0; + + CGContextSaveGState(context); + + for (var j = 0; j < entries.count; j++) + { + var newangle = drawAngles[cnt]; + var sliceSpace = dataSet.sliceSpace; + + var e = entries[j]; + + // draw only if the value is greater than zero + if ((abs(e.value) > 0.000001)) + { + if (!_chart.needsHighlight(xIndex: e.xIndex, + dataSetIndex: _chart.data!.indexOfDataSet(dataSet))) + { + var startAngle = angle + sliceSpace / 2.0; + var sweepAngle = newangle * _animator.phaseY + - sliceSpace / 2.0; + var endAngle = startAngle + sweepAngle; + + var path = CGPathCreateMutable(); + CGPathMoveToPoint(path, nil, circleBox.midX, circleBox.midY); + CGPathAddArc(path, nil, circleBox.midX, circleBox.midY, radius, startAngle * ChartUtils.Math.FDEG2RAD, endAngle * ChartUtils.Math.FDEG2RAD, false); + CGPathCloseSubpath(path); + + if (innerRadius > 0.0) + { + CGPathMoveToPoint(path, nil, circleBox.midX, circleBox.midY); + CGPathAddArc(path, nil, circleBox.midX, circleBox.midY, innerRadius, startAngle * ChartUtils.Math.FDEG2RAD, endAngle * ChartUtils.Math.FDEG2RAD, false); + CGPathCloseSubpath(path); + } + + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + CGContextEOFillPath(context); + } + } + + angle += newangle * _animator.phaseX; + cnt++; + } + + CGContextRestoreGState(context); + } + + public override func drawValues(#context: CGContext) + { + var center = _chart.centerCircleBox; + + // get whole the radius + var r = _chart.radius; + var rotationAngle = _chart.rotationAngle; + var drawAngles = _chart.drawAngles; + var absoluteAngles = _chart.absoluteAngles; + + var off = r / 3.0; + + if (drawHoleEnabled) + { + off = (r - (r * _chart.holeRadiusPercent)) / 2.0; + } + + r -= off; // offset to keep things inside the chart + + var data: ChartData! = _chart.data; + if (data === nil) + { + return; + } + + var defaultValueFormatter = _chart.valueFormatter; + + var dataSets = data.dataSets; + + var cnt = 0; + + for (var i = 0; i < dataSets.count; i++) + { + var dataSet = dataSets[i] as! PieChartDataSet; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + var valueFont = dataSet.valueFont; + var valueTextColor = dataSet.valueTextColor; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + var entries = dataSet.yVals; + + for (var j = 0, maxEntry = Int(CGFloat(entries.count) * _animator.phaseX); j < maxEntry; j++) + { + // offset needed to center the drawn text in the slice + var offset = drawAngles[cnt] / 2.0; + + // calculate the text position + var x = (r * cos(((rotationAngle + absoluteAngles[cnt] - offset) * _animator.phaseY) * ChartUtils.Math.FDEG2RAD) + center.x); + var y = (r * sin(((rotationAngle + absoluteAngles[cnt] - offset) * _animator.phaseY) * ChartUtils.Math.FDEG2RAD) + center.y); + + var value = usePercentValuesEnabled ? entries[j].value / _chart.yValueSum * 100.0 : entries[j].value; + + var val = formatter!.stringFromNumber(value)!; + + var drawXVals = drawXLabelsEnabled; + var drawYVals = dataSet.isDrawValuesEnabled; + + var lineHeight = valueFont.lineHeight; + y -= lineHeight; + + // draw everything, depending on settings + if (drawXVals && drawYVals) + { + y += lineHeight / 2.0; + + ChartUtils.drawText(context: context, text: val, point: CGPoint(x: x, y: y), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + + if (j < data.xValCount) + { + ChartUtils.drawText(context: context, text: data.xVals[j], point: CGPoint(x: x, y: y - lineHeight), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + } + else if (drawXVals && !drawYVals) + { + if (j < data.xValCount) + { + ChartUtils.drawText(context: context, text: data.xVals[j], point: CGPoint(x: x, y: y), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + } + else if (!drawXVals && drawYVals) + { + ChartUtils.drawText(context: context, text: val, point: CGPoint(x: x, y: y), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + + cnt++; + } + } + } + + public override func drawExtras(#context: CGContext) + { + drawHole(context: context); + drawCenterText(context: context); + } + + /// draws the hole in the center of the chart and the transparent circle / hole + private func drawHole(#context: CGContext) + { + if (_chart.drawHoleEnabled) + { + CGContextSaveGState(context); + + var radius = _chart.radius; + var holeRadius = radius * holeRadiusPercent; + var center = _chart.centerCircleBox; + + if (holeColor !== nil && holeColor != UIColor.clearColor()) + { + // draw the hole-circle + CGContextSetFillColorWithColor(context, holeColor!.CGColor); + CGContextFillEllipseInRect(context, CGRect(x: center.x - holeRadius, y: center.y - holeRadius, width: holeRadius * 2.0, height: holeRadius * 2.0)); + } + + if (transparentCircleRadiusPercent > holeRadiusPercent) + { + var secondHoleRadius = radius * transparentCircleRadiusPercent; + + // make transparent + CGContextSetFillColorWithColor(context, holeColor!.colorWithAlphaComponent(CGFloat(0x60) / CGFloat(0xFF)).CGColor); + + // draw the transparent-circle + CGContextFillEllipseInRect(context, CGRect(x: center.x - secondHoleRadius, y: center.y - secondHoleRadius, width: secondHoleRadius * 2.0, height: secondHoleRadius * 2.0)); + } + + CGContextRestoreGState(context); + } + } + + /// draws the description text in the center of the pie chart makes most + /// sense when center-hole is enabled + private func drawCenterText(#context: CGContext) + { + if (drawCenterTextEnabled && centerText != nil) + { + var center = _chart.centerCircleBox; + + // get all lines from the text + var lines = centerText.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()); + + // calculate the height for each line + var lineHeight = centerTextFont.lineHeight; + + var totalheight = lineHeight * CGFloat(lines.count); + + var cnt = lines.count; + + var y = center.y; + + for (var i = 0; i < lines.count; i++) + { + var line = lines[lines.count - i - 1]; + + ChartUtils.drawText(context: context, text: line, point: CGPoint(x: center.x, y: y - lineHeight + lineHeight * CGFloat(cnt) - totalheight / 2.0), align: .Center, attributes: [NSFontAttributeName: centerTextFont, NSForegroundColorAttributeName: centerTextColor]); + + cnt--; + } + } + } + + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + if (_chart.data === nil) + { + return; + } + + CGContextSaveGState(context); + + var rotationAngle = _chart.rotationAngle; + var angle = CGFloat(0.0); + + var drawAngles = _chart.drawAngles; + var absoluteAngles = _chart.absoluteAngles; + + var innerRadius = drawHoleEnabled && holeTransparent ? _chart.radius * holeRadiusPercent : 0.0; + + for (var i = 0; i < indices.count; i++) + { + // get the index to highlight + var xIndex = indices[i].xIndex; + if (xIndex >= drawAngles.count) + { + continue; + } + + if (xIndex == 0) + { + angle = rotationAngle; + } + else + { + angle = rotationAngle + absoluteAngles[xIndex - 1]; + } + + angle *= _animator.phaseY; + + var sliceDegrees = drawAngles[xIndex]; + + var set = _chart.data?.getDataSetByIndex(indices[i].dataSetIndex) as! PieChartDataSet!; + + if (set === nil) + { + continue; + } + + var shift = set.selectionShift; + var circleBox = _chart.circleBox; + + var highlighted = CGRect( + x: circleBox.origin.x - shift, + y: circleBox.origin.y - shift, + width: circleBox.size.width + shift * 2.0, + height: circleBox.size.height + shift * 2.0); + + CGContextSetFillColorWithColor(context, set.colorAt(xIndex).CGColor); + + // redefine the rect that contains the arc so that the highlighted pie is not cut off + + var startAngle = angle + set.sliceSpace / 2.0; + var sweepAngle = sliceDegrees * _animator.phaseY - set.sliceSpace / 2.0; + var endAngle = startAngle + sweepAngle; + + var path = CGPathCreateMutable(); + CGPathMoveToPoint(path, nil, highlighted.midX, highlighted.midY); + CGPathAddArc(path, nil, highlighted.midX, highlighted.midY, highlighted.size.width / 2.0, startAngle * ChartUtils.Math.FDEG2RAD, endAngle * ChartUtils.Math.FDEG2RAD, false); + CGPathCloseSubpath(path); + + if (innerRadius > 0.0) + { + CGPathMoveToPoint(path, nil, highlighted.midX, highlighted.midY); + CGPathAddArc(path, nil, highlighted.midX, highlighted.midY, innerRadius, startAngle * ChartUtils.Math.FDEG2RAD, endAngle * ChartUtils.Math.FDEG2RAD, false); + CGPathCloseSubpath(path); + } + + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextEOFillPath(context); + } + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/RadarChartRenderer.swift b/Charts/Classes/Renderers/RadarChartRenderer.swift new file mode 100644 index 0000000000..23888b4f23 --- /dev/null +++ b/Charts/Classes/Renderers/RadarChartRenderer.swift @@ -0,0 +1,280 @@ +// +// RadarChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class RadarChartRenderer: ChartDataRendererBase +{ + internal weak var _chart: RadarChartView!; + + public init(chart: RadarChartView, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + _chart = chart; + } + + public override func drawData(#context: CGContext) + { + if (_chart !== nil) + { + var radarData = _chart.data; + + if (radarData != nil) + { + for set in radarData!.dataSets as! [RadarChartDataSet] + { + if (set.isVisible) + { + drawDataSet(context: context, dataSet: set); + } + } + } + } + } + + internal func drawDataSet(#context: CGContext, dataSet: RadarChartDataSet) + { + CGContextSaveGState(context); + + var sliceangle = _chart.sliceAngle; + + // calculate the factor that is needed for transforming the value to pixels + var factor = _chart.factor; + + var center = _chart.centerOffsets; + + var entries = dataSet.yVals; + + var path = CGPathCreateMutable(); + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + + var p = ChartUtils.getPosition(center: center, dist: CGFloat(e.value - _chart.chartYMin) * factor, angle: sliceangle * CGFloat(j) + _chart.rotationAngle); + + if (j == 0) + { + CGPathMoveToPoint(path, nil, p.x, p.y); + } + else + { + CGPathAddLineToPoint(path, nil, p.x, p.y); + } + } + + CGPathCloseSubpath(path); + + // draw filled + if (dataSet.isDrawFilledEnabled) + { + CGContextSetFillColorWithColor(context, dataSet.colorAt(0).CGColor); + CGContextSetAlpha(context, dataSet.fillAlpha); + + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextFillPath(context); + } + + // draw the line (only if filled is disabled or alpha is below 255) + if (!dataSet.isDrawFilledEnabled || dataSet.fillAlpha < 1.0) + { + CGContextSetStrokeColorWithColor(context, dataSet.colorAt(0).CGColor) + CGContextSetLineWidth(context, dataSet.lineWidth); + CGContextSetAlpha(context, 1.0); + + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextStrokePath(context); + } + + CGContextRestoreGState(context); + } + + public override func drawValues(#context: CGContext) + { + if (_chart.data === nil) + { + return; + } + + var data = _chart.data!; + + var defaultValueFormatter = _chart.valueFormatter; + + var sliceangle = _chart.sliceAngle; + + // calculate the factor that is needed for transforming the value to pixels + var factor = _chart.factor; + + var center = _chart.centerOffsets; + + var yoffset = CGFloat(5.0); + + for (var i = 0, count = data.dataSetCount; i < count; i++) + { + var dataSet = data.getDataSetByIndex(i) as! RadarChartDataSet; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + var entries = dataSet.yVals; + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + + var p = ChartUtils.getPosition(center: center, dist: CGFloat(e.value) * factor, angle: sliceangle * CGFloat(j) + _chart.rotationAngle); + + var valueFont = dataSet.valueFont; + var valueTextColor = dataSet.valueTextColor; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + ChartUtils.drawText(context: context, text: formatter!.stringFromNumber(e.value)!, point: CGPoint(x: p.x, y: p.y - yoffset - valueFont.lineHeight), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + } + } + + public override func drawExtras(#context: CGContext) + { + drawWeb(context: context); + } + + internal func drawWeb(#context: CGContext) + { + var sliceangle = _chart.sliceAngle; + + CGContextSaveGState(context); + + // calculate the factor that is needed for transforming the value to + // pixels + var factor = _chart.factor; + var rotationangle = _chart.rotationAngle; + + var center = _chart.centerOffsets; + + var lineSegments = UnsafeMutablePointer.alloc(2) + + // draw the web lines that come from the center + CGContextSetLineWidth(context, _chart.webLineWidth); + CGContextSetStrokeColorWithColor(context, _chart.webColor.CGColor); + CGContextSetAlpha(context, _chart.webAlpha); + + for (var i = 0, xValCount = _chart.data!.xValCount; i < xValCount; i++) + { + var p = ChartUtils.getPosition(center: center, dist: CGFloat(_chart.yRange) * factor, angle: sliceangle * CGFloat(i) + rotationangle); + + lineSegments[0].x = center.x; + lineSegments[0].y = center.y; + lineSegments[1].x = p.x; + lineSegments[1].y = p.y; + + CGContextStrokeLineSegments(context, lineSegments, 2); + } + + // draw the inner-web + CGContextSetLineWidth(context, _chart.innerWebLineWidth); + CGContextSetStrokeColorWithColor(context, _chart.innerWebColor.CGColor); + CGContextSetAlpha(context, _chart.webAlpha); + + var labelCount = _chart.yAxis.entryCount; + + for (var j = 0; j < labelCount; j++) + { + for (var i = 0, xValCount = _chart.data!.xValCount; i < xValCount; i++) + { + var r = CGFloat(_chart.yAxis.entries[j] - _chart.chartYMin) * factor; + + var p1 = ChartUtils.getPosition(center: center, dist: r, angle: sliceangle * CGFloat(i) + rotationangle); + var p2 = ChartUtils.getPosition(center: center, dist: r, angle: sliceangle * CGFloat(i + 1) + rotationangle); + + lineSegments[0].x = p1.x; + lineSegments[0].y = p1.y; + lineSegments[1].x = p2.x; + lineSegments[1].y = p2.y; + + CGContextStrokeLineSegments(context, lineSegments, 2); + } + } + + lineSegments.dealloc(2); + + CGContextRestoreGState(context); + } + + private var _lineSegments = [CGPoint](count: 4, repeatedValue: CGPoint()); + + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + if (_chart.data === nil) + { + return; + } + + var data = _chart.data as! RadarChartData; + + CGContextSaveGState(context); + CGContextSetLineWidth(context, data.highlightLineWidth); + if (data.highlightLineDashLengths != nil) + { + CGContextSetLineDash(context, data.highlightLineDashPhase, data.highlightLineDashLengths!, data.highlightLineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var sliceangle = _chart.sliceAngle; + var factor = _chart.factor; + + var center = _chart.centerOffsets; + + for (var i = 0; i < indices.count; i++) + { + var set = _chart.data?.getDataSetByIndex(indices[i].dataSetIndex) as! RadarChartDataSet!; + + if (set === nil) + { + continue; + } + + CGContextSetStrokeColorWithColor(context, set.highlightColor.CGColor); + + // get the index to highlight + var xIndex = indices[i].xIndex; + + var e = set.entryForXIndex(xIndex); + var j = set.entryIndex(entry: e, isEqual: true); + var y = (e.value - _chart.chartYMin); + + var p = ChartUtils.getPosition(center: center, dist: CGFloat(y) * factor, + angle: sliceangle * CGFloat(j) + _chart.rotationAngle); + + _lineSegments[0] = CGPoint(x: p.x, y: 0.0) + _lineSegments[1] = CGPoint(x: p.x, y: viewPortHandler.chartHeight) + _lineSegments[2] = CGPoint(x: 0.0, y: p.y) + _lineSegments[3] = CGPoint(x: viewPortHandler.chartWidth, y: p.y) + CGContextStrokeLineSegments(context, _lineSegments, 4); + } + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Renderers/ScatterChartRenderer.swift b/Charts/Classes/Renderers/ScatterChartRenderer.swift new file mode 100644 index 0000000000..c267f24d75 --- /dev/null +++ b/Charts/Classes/Renderers/ScatterChartRenderer.swift @@ -0,0 +1,298 @@ +// +// ScatterChartRenderer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 4/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +@objc +public protocol ScatterChartRendererDelegate +{ + func scatterChartRendererData(renderer: ScatterChartRenderer) -> ScatterChartData!; + func scatterChartRenderer(renderer: ScatterChartRenderer, transformerForAxis which: ChartYAxis.AxisDependency) -> ChartTransformer!; + func scatterChartDefaultRendererValueFormatter(renderer: ScatterChartRenderer) -> NSNumberFormatter!; + func scatterChartRendererChartYMax(renderer: ScatterChartRenderer) -> Float; + func scatterChartRendererChartYMin(renderer: ScatterChartRenderer) -> Float; + func scatterChartRendererChartXMax(renderer: ScatterChartRenderer) -> Float; + func scatterChartRendererChartXMin(renderer: ScatterChartRenderer) -> Float; + func scatterChartRendererMaxVisibleValueCount(renderer: ScatterChartRenderer) -> Int; +} + +public class ScatterChartRenderer: ChartDataRendererBase +{ + public weak var delegate: ScatterChartRendererDelegate?; + + public init(delegate: ScatterChartRendererDelegate?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) + { + super.init(animator: animator, viewPortHandler: viewPortHandler); + + self.delegate = delegate; + } + + public override func drawData(#context: CGContext) + { + var scatterData = delegate!.scatterChartRendererData(self); + + if (scatterData === nil) + { + return; + } + + for (var i = 0; i < scatterData.dataSetCount; i++) + { + var set = scatterData.getDataSetByIndex(i); + + if (set !== nil && set!.isVisible) + { + drawDataSet(context: context, dataSet: set as! ScatterChartDataSet); + } + } + } + + private var _lineSegments = [CGPoint](count: 2, repeatedValue: CGPoint()); + + internal func drawDataSet(#context: CGContext, dataSet: ScatterChartDataSet) + { + var trans = delegate!.scatterChartRenderer(self, transformerForAxis: dataSet.axisDependency); + calcXBounds(trans); + + var phaseX = _animator.phaseX; + var phaseY = _animator.phaseY; + + var entries = dataSet.yVals; + + var shapeSize = dataSet.scatterShapeSize; + var shapeHalf = shapeSize / 2.0; + + var point = CGPoint(); + + var valueToPixelMatrix = trans.valueToPixelMatrix; + + var shape = dataSet.scatterShape; + + CGContextSaveGState(context); + + for (var j = 0, count = Int(ceil(CGFloat(entries.count) * _animator.phaseX)); j < count; j++) + { + var e = entries[j]; + point.x = CGFloat(e.xIndex); + point.y = CGFloat(e.value) * phaseY; + point = CGPointApplyAffineTransform(point, valueToPixelMatrix); + + if (!viewPortHandler.isInBoundsRight(point.x)) + { + break; + } + + if (!viewPortHandler.isInBoundsLeft(point.x) || !viewPortHandler.isInBoundsY(point.y)) + { + continue; + } + + if (shape == .Square) + { + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + var rect = CGRect(); + rect.origin.x = point.x - shapeHalf; + rect.origin.y = point.y - shapeHalf; + rect.size.width = shapeSize; + rect.size.height = shapeSize; + CGContextFillRect(context, rect); + } + else if (shape == .Circle) + { + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + var rect = CGRect(); + rect.origin.x = point.x - shapeHalf; + rect.origin.y = point.y - shapeHalf; + rect.size.width = shapeSize; + rect.size.height = shapeSize; + CGContextFillEllipseInRect(context, rect); + } + else if (shape == .Cross) + { + CGContextSetStrokeColorWithColor(context, dataSet.colorAt(j).CGColor); + _lineSegments[0].x = point.x - shapeHalf; + _lineSegments[0].y = point.y; + _lineSegments[1].x = point.x + shapeHalf; + _lineSegments[1].y = point.y; + CGContextStrokeLineSegments(context, _lineSegments, 2); + + _lineSegments[0].x = point.x; + _lineSegments[0].y = point.y - shapeHalf; + _lineSegments[1].x = point.x; + _lineSegments[1].y = point.y + shapeHalf; + CGContextStrokeLineSegments(context, _lineSegments, 2); + } + else if (shape == .Triangle) + { + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + + // create a triangle path + CGContextBeginPath(context); + CGContextMoveToPoint(context, point.x, point.y - shapeHalf); + CGContextAddLineToPoint(context, point.x + shapeHalf, point.y + shapeHalf); + CGContextAddLineToPoint(context, point.x - shapeHalf, point.y + shapeHalf); + CGContextClosePath(context); + + CGContextFillPath(context); + } + else if (shape == .Custom) + { + CGContextSetFillColorWithColor(context, dataSet.colorAt(j).CGColor); + + var customShape = dataSet.customScatterShape + + if (customShape === nil) + { + return; + } + + // transform the provided custom path + CGContextSaveGState(context); + CGContextTranslateCTM(context, -point.x, -point.y); + + CGContextBeginPath(context); + CGContextAddPath(context, customShape); + CGContextFillPath(context); + + CGContextRestoreGState(context); + } + } + + CGContextRestoreGState(context); + } + + public override func drawValues(#context: CGContext) + { + var scatterData = delegate!.scatterChartRendererData(self); + if (scatterData === nil) + { + return; + } + + var defaultValueFormatter = delegate!.scatterChartDefaultRendererValueFormatter(self); + + // if values are drawn + if (scatterData.yValCount < Int(ceil(CGFloat(delegate!.scatterChartRendererMaxVisibleValueCount(self)) * viewPortHandler.scaleX))) + { + var dataSets = scatterData.dataSets as! [ScatterChartDataSet]; + + for (var i = 0; i < scatterData.dataSetCount; i++) + { + var dataSet = dataSets[i]; + + if (!dataSet.isDrawValuesEnabled) + { + continue; + } + + var valueFont = dataSet.valueFont; + var valueTextColor = dataSet.valueTextColor; + + var formatter = dataSet.valueFormatter; + if (formatter === nil) + { + formatter = defaultValueFormatter; + } + + var entries = dataSet.yVals; + + var positions = delegate!.scatterChartRenderer(self, transformerForAxis: dataSet.axisDependency).generateTransformedValuesScatter(entries, phaseY: _animator.phaseY); + + var shapeSize = dataSet.scatterShapeSize; + var lineHeight = valueFont.lineHeight; + + for (var j = 0, count = Int(ceil(CGFloat(positions.count) * _animator.phaseX)); j < count; j++) + { + if (!viewPortHandler.isInBoundsRight(positions[j].x)) + { + break; + } + + // make sure the lines don't do shitty things outside bounds + if (j != 0 && (!viewPortHandler.isInBoundsLeft(positions[j].x) + || !viewPortHandler.isInBoundsY(positions[j].y))) + { + continue; + } + + var val = entries[j].value; + + var text = formatter!.stringFromNumber(val); + + ChartUtils.drawText(context: context, text: text!, point: CGPoint(x: positions[j].x, y: positions[j].y - shapeSize - lineHeight), align: .Center, attributes: [NSFontAttributeName: valueFont, NSForegroundColorAttributeName: valueTextColor]); + } + } + } + } + + public override func drawExtras(#context: CGContext) + { + + } + + public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight]) + { + var scatterData = delegate!.scatterChartRendererData(self); + var chartXMax = delegate!.scatterChartRendererChartXMax(self); + var chartYMax = delegate!.scatterChartRendererChartYMax(self); + var chartYMin = delegate!.scatterChartRendererChartYMin(self); + + CGContextSaveGState(context); + + var pts = [CGPoint](count: 4, repeatedValue: CGPoint()); + + for (var i = 0; i < indices.count; i++) + { + var set = scatterData.getDataSetByIndex(indices[i].dataSetIndex) as! ScatterChartDataSet!; + + if (set === nil) + { + continue; + } + + CGContextSetStrokeColorWithColor(context, set.highlightColor.CGColor); + CGContextSetLineWidth(context, set.highlightLineWidth); + if (set.highlightLineDashLengths != nil) + { + CGContextSetLineDash(context, set.highlightLineDashPhase, set.highlightLineDashLengths!, set.highlightLineDashLengths!.count); + } + else + { + CGContextSetLineDash(context, 0.0, nil, 0); + } + + var xIndex = indices[i].xIndex; // get the x-position + + if (CGFloat(xIndex) > CGFloat(chartXMax) * _animator.phaseX) + { + continue; + } + + var y = CGFloat(set.yValForXIndex(xIndex)) * _animator.phaseY; // get the y-position + + pts[0] = CGPoint(x: CGFloat(xIndex), y: CGFloat(chartYMax)); + pts[1] = CGPoint(x: CGFloat(xIndex), y: CGFloat(chartYMin)); + pts[2] = CGPoint(x: 0.0, y: y); + pts[3] = CGPoint(x: CGFloat(chartXMax), y: y); + + var trans = delegate!.scatterChartRenderer(self, transformerForAxis: set.axisDependency); + + trans.pointValuesToPixel(&pts); + + // draw the highlight lines + CGContextStrokeLineSegments(context, pts, pts.count); + } + + CGContextRestoreGState(context); + } +} \ No newline at end of file diff --git a/Charts/Classes/Utils/ChartColorTemplates.swift b/Charts/Classes/Utils/ChartColorTemplates.swift new file mode 100644 index 0000000000..c6599520f2 --- /dev/null +++ b/Charts/Classes/Utils/ChartColorTemplates.swift @@ -0,0 +1,74 @@ +// +// ChartColorTemplates.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit + +public class ChartColorTemplates: NSObject +{ + public class func liberty () -> [UIColor] + { + return [ + UIColor(red: 207/255.0, green: 248/255.0, blue: 246/255.0, alpha: 1.0), + UIColor(red: 148/255.0, green: 212/255.0, blue: 212/255.0, alpha: 1.0), + UIColor(red: 136/255.0, green: 180/255.0, blue: 187/255.0, alpha: 1.0), + UIColor(red: 118/255.0, green: 174/255.0, blue: 175/255.0, alpha: 1.0), + UIColor(red: 42/255.0, green: 109/255.0, blue: 130/255.0, alpha: 1.0) + ]; + } + + public class func joyful () -> [UIColor] + { + return [ + UIColor(red: 217/255.0, green: 80/255.0, blue: 138/255.0, alpha: 1.0), + UIColor(red: 254/255.0, green: 149/255.0, blue: 7/255.0, alpha: 1.0), + UIColor(red: 254/255.0, green: 247/255.0, blue: 120/255.0, alpha: 1.0), + UIColor(red: 106/255.0, green: 167/255.0, blue: 134/255.0, alpha: 1.0), + UIColor(red: 53/255.0, green: 194/255.0, blue: 209/255.0, alpha: 1.0) + ]; + } + + public class func pastel () -> [UIColor] + { + return [ + UIColor(red: 64/255.0, green: 89/255.0, blue: 128/255.0, alpha: 1.0), + UIColor(red: 149/255.0, green: 165/255.0, blue: 124/255.0, alpha: 1.0), + UIColor(red: 217/255.0, green: 184/255.0, blue: 162/255.0, alpha: 1.0), + UIColor(red: 191/255.0, green: 134/255.0, blue: 134/255.0, alpha: 1.0), + UIColor(red: 179/255.0, green: 48/255.0, blue: 80/255.0, alpha: 1.0) + ]; + } + + public class func colorful () -> [UIColor] + { + return [ + UIColor(red: 193/255.0, green: 37/255.0, blue: 82/255.0, alpha: 1.0), + UIColor(red: 255/255.0, green: 102/255.0, blue: 0/255.0, alpha: 1.0), + UIColor(red: 245/255.0, green: 199/255.0, blue: 0/255.0, alpha: 1.0), + UIColor(red: 106/255.0, green: 150/255.0, blue: 31/255.0, alpha: 1.0), + UIColor(red: 179/255.0, green: 100/255.0, blue: 53/255.0, alpha: 1.0) + ]; + } + + public class func vordiplom () -> [UIColor] + { + return [ + UIColor(red: 192/255.0, green: 255/255.0, blue: 140/255.0, alpha: 1.0), + UIColor(red: 255/255.0, green: 247/255.0, blue: 140/255.0, alpha: 1.0), + UIColor(red: 255/255.0, green: 208/255.0, blue: 140/255.0, alpha: 1.0), + UIColor(red: 140/255.0, green: 234/255.0, blue: 255/255.0, alpha: 1.0), + UIColor(red: 255/255.0, green: 140/255.0, blue: 157/255.0, alpha: 1.0) + ]; + } +} \ No newline at end of file diff --git a/Charts/Classes/Utils/ChartFillFormatter.swift b/Charts/Classes/Utils/ChartFillFormatter.swift new file mode 100644 index 0000000000..85b1d77ec5 --- /dev/null +++ b/Charts/Classes/Utils/ChartFillFormatter.swift @@ -0,0 +1,22 @@ +// +// ChartFillFormatter.swift +// Charts +// +// Created by Daniel Cohen Gindi on 6/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Protocol for providing a custom logic to where the filling line of a DataSet should end. If setFillEnabled(...) is set to true. +@objc +public protocol ChartFillFormatter +{ + /// Returns the vertical (y-axis) position where the filled-line of the DataSet should end. + func getFillLinePosition(#dataSet: LineChartDataSet, data: LineChartData, chartMaxY: Float, chartMinY: Float) -> CGFloat; +} diff --git a/Charts/Classes/Utils/ChartHighlight.swift b/Charts/Classes/Utils/ChartHighlight.swift new file mode 100644 index 0000000000..daf427e6ed --- /dev/null +++ b/Charts/Classes/Utils/ChartHighlight.swift @@ -0,0 +1,121 @@ +// +// ChartHighlight.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartHighlight: NSObject +{ + /// the x-index of the highlighted value + private var _xIndex = Int(0) + + /// the index of the dataset the highlighted value is in + private var _dataSetIndex = Int(0) + + /// index which value of a stacked bar entry is highlighted + /// :default: -1 + private var _stackIndex = Int(-1) + + public override init() + { + super.init(); + } + + public init(xIndex x: Int, dataSetIndex: Int) + { + super.init(); + + _xIndex = x; + _dataSetIndex = dataSetIndex; + } + + public init(xIndex x: Int, dataSetIndex: Int, stackIndex: Int) + { + super.init(); + + _xIndex = x; + _dataSetIndex = dataSetIndex; + _stackIndex = stackIndex; + } + + public var dataSetIndex: Int { return _dataSetIndex; } + public var xIndex: Int { return _xIndex; } + public var stackIndex: Int { return _stackIndex; } + + // MARK: NSObject + + public override var description: String + { + return "Highlight, xIndex: \(_xIndex), dataSetIndex: \(_dataSetIndex), stackIndex (only stacked barentry): \(_stackIndex)"; + } + + public override func isEqual(object: AnyObject?) -> Bool + { + if (object == nil) + { + return false; + } + + if (!object!.isKindOfClass(self.dynamicType)) + { + return false; + } + + if (object!.xIndex != _xIndex) + { + return false; + } + + if (object!.dataSetIndex != _dataSetIndex) + { + return false; + } + + if (object!.stackIndex != _stackIndex) + { + return false; + } + + return true; + } +} + +func ==(lhs: ChartHighlight, rhs: ChartHighlight) -> Bool +{ + if (lhs === rhs) + { + return true; + } + + if (!lhs.isKindOfClass(rhs.dynamicType)) + { + return false; + } + + if (lhs._xIndex != rhs._xIndex) + { + return false; + } + + if (lhs._dataSetIndex != rhs._dataSetIndex) + { + return false; + } + + if (lhs._stackIndex != rhs._stackIndex) + { + return false; + } + + return true; +} \ No newline at end of file diff --git a/Charts/Classes/Utils/ChartSelInfo.swift b/Charts/Classes/Utils/ChartSelInfo.swift new file mode 100644 index 0000000000..948b4a101b --- /dev/null +++ b/Charts/Classes/Utils/ChartSelInfo.swift @@ -0,0 +1,113 @@ +// +// ChartselInfo.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartSelInfo: NSObject +{ + private var _value = Float(0) + private var _dataSetIndex = Int(0) + private var _dataSet: ChartDataSet! + + public override init() + { + super.init(); + } + + public init(value: Float, dataSetIndex: Int, dataSet: ChartDataSet) + { + super.init(); + + _value = value; + _dataSetIndex = dataSetIndex; + _dataSet = dataSet; + } + + public var value: Float + { + return _value; + } + + public var dataSetIndex: Int + { + return _dataSetIndex; + } + + public var dataSet: ChartDataSet? + { + return _dataSet; + } + + // MARK: NSObject + + public override func isEqual(object: AnyObject?) -> Bool + { + if (object == nil) + { + return false; + } + + if (!object!.isKindOfClass(self.dynamicType)) + { + return false; + } + + if (object!.value != _value) + { + return false; + } + + if (object!.dataSetIndex != _dataSetIndex) + { + return false; + } + + if (object!.dataSet !== _dataSet) + { + return false; + } + + return true; + } +} + +public func ==(lhs: ChartSelInfo, rhs: ChartSelInfo) -> Bool +{ + if (lhs === rhs) + { + return true; + } + + if (!lhs.isKindOfClass(rhs.dynamicType)) + { + return false; + } + + if (lhs.value != rhs.value) + { + return false; + } + + if (lhs.dataSetIndex != rhs.dataSetIndex) + { + return false; + } + + if (lhs.dataSet !== rhs.dataSet) + { + return false; + } + + return true; +} \ No newline at end of file diff --git a/Charts/Classes/Utils/ChartTransformer.swift b/Charts/Classes/Utils/ChartTransformer.swift new file mode 100644 index 0000000000..4c328cf043 --- /dev/null +++ b/Charts/Classes/Utils/ChartTransformer.swift @@ -0,0 +1,269 @@ +// +// ChartTransformer.swift +// Charts +// +// Created by Daniel Cohen Gindi on 6/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +/// Transformer class that contains all matrices and is responsible for transforming values into pixels on the screen and backwards. +public class ChartTransformer: NSObject +{ + /// matrix to map the values to the screen pixels + internal var _matrixValueToPx = CGAffineTransformIdentity + + /// matrix for handling the different offsets of the chart + internal var _matrixOffset = CGAffineTransformIdentity + + private var _viewPortHandler: ChartViewPortHandler + + public init(viewPortHandler: ChartViewPortHandler) + { + _viewPortHandler = viewPortHandler; + } + + /// Prepares the matrix that transforms values to pixels. Calculates the scale factors from the charts size and offsets. + public func prepareMatrixValuePx(#chartXMin: Float, deltaX: CGFloat, deltaY: CGFloat, chartYMin: Float) + { + var scaleX = ((_viewPortHandler.chartWidth - _viewPortHandler.offsetRight - _viewPortHandler.offsetLeft) / deltaX); + var scaleY = ((_viewPortHandler.chartHeight - _viewPortHandler.offsetTop - _viewPortHandler.offsetBottom) / deltaY); + + // setup all matrices + _matrixValueToPx = CGAffineTransformIdentity; + _matrixValueToPx = CGAffineTransformScale(_matrixValueToPx, scaleX, -scaleY); + _matrixValueToPx = CGAffineTransformTranslate(_matrixValueToPx, CGFloat(-chartXMin), CGFloat(-chartYMin)); + } + + /// Prepares the matrix that contains all offsets. + public func prepareMatrixOffset(inverted: Bool) + { + if (!inverted) + { + _matrixOffset = CGAffineTransformTranslate(CGAffineTransformIdentity, _viewPortHandler.offsetLeft, _viewPortHandler.chartHeight - _viewPortHandler.offsetBottom); + } + else + { + _matrixOffset = CGAffineTransformScale(_matrixOffset, 1.0, -1.0); + _matrixOffset = CGAffineTransformTranslate(CGAffineTransformIdentity, _viewPortHandler.offsetLeft, -_viewPortHandler.offsetTop); + } + } + + /// Transforms an arraylist of Entry into a float array containing the x and y values transformed with all matrices for the SCATTERCHART. + public func generateTransformedValuesScatter(entries: [ChartDataEntry], phaseY: CGFloat) -> [CGPoint] + { + var valuePoints = [CGPoint](); + valuePoints.reserveCapacity(entries.count); + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + valuePoints.append(CGPoint(x: CGFloat(e.xIndex), y: CGFloat(e.value) * phaseY)); + } + + pointValuesToPixel(&valuePoints); + + return valuePoints; + } + + /// Transforms an arraylist of Entry into a float array containing the x and y values transformed with all matrices for the LINECHART. + public func generateTransformedValuesLine(entries: [ChartDataEntry], phaseY: CGFloat) -> [CGPoint] + { + var valuePoints = [CGPoint](); + valuePoints.reserveCapacity(entries.count); + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + valuePoints.append(CGPoint(x: CGFloat(e.xIndex), y: CGFloat(e.value) * phaseY)); + } + + pointValuesToPixel(&valuePoints); + + return valuePoints; + } + + /// Transforms an arraylist of Entry into a float array containing the x and y values transformed with all matrices for the CANDLESTICKCHART. + public func generateTransformedValuesCandle(entries: [CandleChartDataEntry], phaseY: CGFloat) -> [CGPoint] + { + var valuePoints = [CGPoint](); + valuePoints.reserveCapacity(entries.count); + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + valuePoints.append(CGPoint(x: CGFloat(e.xIndex), y: CGFloat(e.high) * phaseY)); + } + + pointValuesToPixel(&valuePoints); + + return valuePoints; + } + + /// Transforms an arraylist of Entry into a float array containing the x and y values transformed with all matrices for the BARCHART. + public func generateTransformedValuesBarChart(entries: [BarChartDataEntry], dataSet: Int, barData: BarChartData, phaseY: CGFloat) -> [CGPoint] + { + var valuePoints = [CGPoint](); + valuePoints.reserveCapacity(entries.count); + + var setCount = barData.dataSetCount; + var space = barData.groupSpace; + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + + // calculate the x-position, depending on datasetcount + var x = CGFloat(e.xIndex + (j * (setCount - 1)) + dataSet) + space * CGFloat(j) + space / 2.0; + var y = e.value; + + valuePoints.append(CGPoint(x: x, y: CGFloat(y) * phaseY)); + } + + pointValuesToPixel(&valuePoints); + + return valuePoints; + } + + /// Transforms an arraylist of Entry into a float array containing the x and y values transformed with all matrices for the BARCHART. + public func generateTransformedValuesHorizontalBarChart(entries: [ChartDataEntry], dataSet: Int, barData: BarChartData, phaseY: CGFloat) -> [CGPoint] + { + var valuePoints = [CGPoint](); + valuePoints.reserveCapacity(entries.count); + + var setCount = barData.dataSetCount; + var space = barData.groupSpace; + + for (var j = 0; j < entries.count; j++) + { + var e = entries[j]; + + // calculate the x-position, depending on datasetcount + var x = CGFloat(e.xIndex + (j * (setCount - 1)) + dataSet) + space * CGFloat(j) + space / 2.0; + var y = e.value; + + valuePoints.append(CGPoint(x: CGFloat(y) * phaseY, y: x)); + } + + pointValuesToPixel(&valuePoints); + + return valuePoints; + } + + /// Transform an array of points with all matrices. + // VERY IMPORTANT: Keep matrix order "value-touch-offset" when transforming. + public func pointValuesToPixel(inout pts: [CGPoint]) + { + var trans = valueToPixelMatrix; + for (var i = 0, count = pts.count; i < count; i++) + { + pts[i] = CGPointApplyAffineTransform(pts[i], trans); + } + } + + public func pointValueToPixel(inout point: CGPoint) + { + point = CGPointApplyAffineTransform(point, valueToPixelMatrix); + } + + /// Transform a rectangle with all matrices. + public func rectValueToPixel(inout r: CGRect) + { + r = CGRectApplyAffineTransform(r, valueToPixelMatrix); + } + + /// Transform a rectangle with all matrices with potential animation phases. + public func rectValueToPixel(inout r: CGRect, phaseY: CGFloat) + { + // multiply the height of the rect with the phase + if (r.origin.y > 0.0) + { + r.origin.y *= phaseY; + } + else + { + var bottom = r.origin.y + r.size.height; + bottom *= phaseY; + r.size.height = bottom - r.origin.y; + } + + r = CGRectApplyAffineTransform(r, valueToPixelMatrix); + } + + /// Transform a rectangle with all matrices with potential animation phases. + public func rectValueToPixelHorizontal(inout r: CGRect, phaseY: CGFloat) + { + // multiply the height of the rect with the phase + if (r.origin.x > 0.0) + { + r.origin.x *= phaseY; + } + else + { + var right = r.origin.x + r.size.width; + right *= phaseY; + r.size.width = right - r.origin.x; + } + + r = CGRectApplyAffineTransform(r, valueToPixelMatrix); + } + + /// transforms multiple rects with all matrices + public func rectValuesToPixel(inout rects: [CGRect]) + { + var trans = valueToPixelMatrix; + + for (var i = 0; i < rects.count; i++) + { + rects[i] = CGRectApplyAffineTransform(rects[i], trans); + } + } + + /// Transforms the given array of touch points (pixels) into values on the chart. + public func pixelsToValue(inout pixels: [CGPoint]) + { + var trans = pixelToValueMatrix; + + for (var i = 0; i < pixels.count; i++) + { + pixels[i] = CGPointApplyAffineTransform(pixels[i], trans); + } + } + + /// Transforms the given touch point (pixels) into a value on the chart. + public func pixelToValue(inout pixel: CGPoint) + { + pixel = CGPointApplyAffineTransform(pixel, pixelToValueMatrix); + } + + /// Returns the x and y values in the chart at the given touch point + /// (encapsulated in a PointD). This method transforms pixel coordinates to + /// coordinates / values in the chart. + public func getValueByTouchPoint(point: CGPoint) -> CGPoint + { + return CGPointApplyAffineTransform(point, pixelToValueMatrix); + } + + public var valueToPixelMatrix: CGAffineTransform + { + return + CGAffineTransformConcat( + CGAffineTransformConcat( + _matrixValueToPx, + _viewPortHandler.touchMatrix + ), + _matrixOffset + ); + } + + public var pixelToValueMatrix: CGAffineTransform + { + return CGAffineTransformInvert(valueToPixelMatrix); + } +} \ No newline at end of file diff --git a/Charts/Classes/Utils/ChartUtils.swift b/Charts/Classes/Utils/ChartUtils.swift new file mode 100644 index 0000000000..acdfe416d8 --- /dev/null +++ b/Charts/Classes/Utils/ChartUtils.swift @@ -0,0 +1,136 @@ +// +// Utils.swift +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit +import Darwin; + +internal class ChartUtils +{ + internal struct Math + { + internal static let FDEG2RAD = CGFloat(M_PI / 180.0); + internal static let FRAD2DEG = CGFloat(180.0 / M_PI); + internal static let DEG2RAD = M_PI / 180.0; + internal static let RAD2DEG = 180.0 / M_PI; + }; + + internal class func roundToNextSignificant(#number: Double) -> Double + { + if (isinf(number) || isnan(number) || number == 0) + { + return number; + } + + let d = ceil(log10(number < 0.0 ? -number : number)); + let pw = 1 - Int(d); + let magnitude = pow(Double(10.0), Double(pw)); + let shifted = round(number * magnitude); + return shifted / magnitude; + } + + internal class func decimals(number: Float) -> Int + { + if (number == 0.0) + { + return 0; + } + + var i = roundToNextSignificant(number: Double(number)); + return Int(ceil(-log10(i))) + 2; + } + + internal class func nextUp(number: Double) -> Double + { + if (isinf(number) || isnan(number)) + { + return number; + } + else + { + return number + DBL_EPSILON; + } + } + + /// Returns the index of the DataSet that contains the closest value on the y-axis. This is needed for highlighting. + internal class func closestDataSetIndex(valsAtIndex: [ChartSelInfo], value: Float, axis: ChartYAxis.AxisDependency?) -> Int + { + var index = -1; + var distance = FLT_MAX; + + for (var i = 0; i < valsAtIndex.count; i++) + { + var sel = valsAtIndex[i]; + + if (axis == nil || sel.dataSet?.axisDependency == axis) + { + var cdistance = abs(sel.value - value); + if (cdistance < distance) + { + index = valsAtIndex[i].dataSetIndex; + distance = cdistance; + } + } + } + + return index; + } + + /// Returns the minimum distance from a touch-y-value (in pixels) to the closest y-value (in pixels) that is displayed in the chart. + internal class func getMinimumDistance(valsAtIndex: [ChartSelInfo], val: Float, axis: ChartYAxis.AxisDependency) -> Float + { + var distance = FLT_MAX; + + for (var i = 0, count = valsAtIndex.count; i < count; i++) + { + var sel = valsAtIndex[i]; + + if (sel.dataSet!.axisDependency == axis) + { + var cdistance = abs(sel.value - val); + if (cdistance < distance) + { + distance = cdistance; + } + } + } + + return distance; + } + + /// Calculates the position around a center point, depending on the distance from the center, and the angle of the position around the center. + internal class func getPosition(#center: CGPoint, dist: CGFloat, angle: CGFloat) -> CGPoint + { + return CGPoint( + x: center.x + dist * cos(angle * Math.FDEG2RAD), + y: center.y + dist * sin(angle * Math.FDEG2RAD) + ); + } + + internal class func drawText(#context: CGContext, text: String, var point: CGPoint, align: NSTextAlignment, attributes: [NSObject : AnyObject]?) + { + if (align == .Center) + { + point.x -= text.sizeWithAttributes(attributes).width / 2.0; + } + else if (align == .Right) + { + point.x -= text.sizeWithAttributes(attributes).width; + } + + UIGraphicsPushContext(context); + (text as NSString).drawAtPoint(point, withAttributes: attributes); + UIGraphicsPopContext(); + } +} \ No newline at end of file diff --git a/Charts/Classes/Utils/ChartViewPortHandler.swift b/Charts/Classes/Utils/ChartViewPortHandler.swift new file mode 100644 index 0000000000..0a9a328649 --- /dev/null +++ b/Charts/Classes/Utils/ChartViewPortHandler.swift @@ -0,0 +1,377 @@ +// +// ChartViewPortHandler.swift +// Charts +// +// Created by Daniel Cohen Gindi on 27/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartViewPortHandler +{ + /// matrix used for touch events + private var _touchMatrix = CGAffineTransformIdentity; + + /// this rectangle defines the area in which graph values can be drawn + private var _contentRect = CGRect(); + + private var _chartWidth = CGFloat(0.0); + private var _chartHeight = CGFloat(0.0); + + /// minimum scale value on the y-axis + private var _minScaleY = CGFloat(1.0); + + /// minimum scale value on the x-axis + private var _minScaleX = CGFloat(1.0); + + /// contains the current scale factor of the x-axis + private var _scaleX = CGFloat(1.0); + + /// contains the current scale factor of the y-axis + private var _scaleY = CGFloat(1.0); + + /// offset that allows the chart to be dragged over its bounds on the x-axis + private var _transOffsetX = CGFloat(0.0); + + /// offset that allows the chart to be dragged over its bounds on the x-axis + private var _transOffsetY = CGFloat(0.0); + + public init() + { + } + + public init(width: CGFloat, height: CGFloat) + { + _chartHeight = height; + _chartWidth = width; + } + + public func setChartDimens(#width: CGFloat, height: CGFloat) + { + _chartHeight = height; + _chartWidth = width; + + if (_contentRect.size.width <= 0.0 || _contentRect.size.height <= 0.0) + { + _contentRect.origin.x = 0.0; + _contentRect.origin.y = 0.0; + _contentRect.size.width = width; + _contentRect.size.height = height; + } + } + + public func restrainViewPort(#offsetLeft: CGFloat, offsetTop: CGFloat, offsetRight: CGFloat, offsetBottom: CGFloat) + { + _contentRect.origin.x = offsetLeft; + _contentRect.origin.y = offsetTop; + _contentRect.size.width = _chartWidth - offsetLeft - offsetRight; + _contentRect.size.height = _chartHeight - offsetBottom - offsetTop; + } + + public var offsetLeft: CGFloat + { + return _contentRect.origin.x; + } + + public var offsetRight: CGFloat + { + return _chartWidth - _contentRect.size.width - _contentRect.origin.x; + } + + public var offsetTop: CGFloat + { + return _contentRect.origin.y; + } + + public var offsetBottom: CGFloat + { + return _chartHeight - _contentRect.size.height - _contentRect.origin.y; + } + + public var contentTop: CGFloat + { + return _contentRect.origin.y; + } + + public var contentLeft: CGFloat + { + return _contentRect.origin.x; + } + + public var contentRight: CGFloat + { + return _contentRect.origin.x + _contentRect.size.width; + } + + public var contentBottom: CGFloat + { + return _contentRect.origin.y + _contentRect.size.height; + } + + public var contentWidth: CGFloat + { + return _contentRect.size.width; + } + + public var contentHeight: CGFloat + { + return _contentRect.size.height; + } + + public var contentRect: CGRect { return _contentRect; } + + public var contentCenter: CGPoint + { + return CGPoint(x: _contentRect.origin.x + _contentRect.size.width / 2.0, y: _contentRect.origin.y + _contentRect.size.height / 2.0); + } + + public var chartHeight: CGFloat { return _chartHeight; } + + public var chartWidth: CGFloat { return _chartWidth; } + + // MARK: - Scaling/Panning etc. + + /// Zooms around the specified center + public func zoom(#scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform + { + var matrix = CGAffineTransformTranslate(_touchMatrix, x, y); + matrix = CGAffineTransformScale(matrix, scaleX, scaleY); + matrix = CGAffineTransformTranslate(matrix, -x, -y); + return matrix; + } + + /// Zooms in by 1.4f, x and y are the coordinates (in pixels) of the zoom center. + public func zoomIn(#x: CGFloat, y: CGFloat) -> CGAffineTransform + { + return zoom(scaleX: 1.4, scaleY: 1.4, x: x, y: y); + } + + /// Zooms out by 0.7f, x and y are the coordinates (in pixels) of the zoom center. + public func zoomOut(#x: CGFloat, y: CGFloat) -> CGAffineTransform + { + return zoom(scaleX: 0.7, scaleY: 0.7, x: x, y: y); + } + + /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. + public func fitScreen() -> CGAffineTransform + { + return CGAffineTransformIdentity; + } + + /// Centers the viewport around the specified position (x-index and y-value) in the chart. + public func centerViewPort(#pt: CGPoint, chart: ChartViewBase) + { + var matrix = CGAffineTransformTranslate( + _touchMatrix, + -(pt.x - offsetLeft), + -(pt.y - offsetTop) + ); + + refresh(newMatrix: matrix, chart: chart, invalidate: false); + } + + /// call this method to refresh the graph with a given matrix + public func refresh(#newMatrix: CGAffineTransform, chart: ChartViewBase, invalidate: Bool) -> CGAffineTransform + { + _touchMatrix = newMatrix; + + // make sure scale and translation are within their bounds + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect); + + chart.setNeedsDisplay(); + + return _touchMatrix; + } + + /// limits the maximum scale and X translation of the given matrix + private func limitTransAndScale(inout #matrix: CGAffineTransform, content: CGRect?) + { + // min scale-x is 1f + _scaleX = max(_minScaleX, matrix.a); + + // min scale-y is 1f + _scaleY = max(_minScaleY, matrix.d); + + var width: CGFloat = 0.0; + var height: CGFloat = 0.0; + + if (content != nil) + { + width = content!.width; + height = content!.height; + } + + var maxTransX = -width * (_scaleX - 1.0); + var newTransX = min(max(matrix.tx, maxTransX - _transOffsetX), _transOffsetX); + + var maxTransY = height * (_scaleY - 1.0); + var newTransY = max(min(matrix.ty, maxTransY + _transOffsetY), -_transOffsetY); + + matrix.tx = newTransX; + matrix.a = _scaleX; + matrix.ty = newTransY; + matrix.d = _scaleY; + } + + public func setMinimumScaleX(var xScale: CGFloat) + { + if (xScale < 1.0) + { + xScale = 1.0; + } + + _minScaleX = xScale; + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect); + } + + public func setMinimumScaleY(var yScale: CGFloat) + { + if (yScale < 1.0) + { + yScale = 1.0; + } + + _minScaleY = yScale; + + limitTransAndScale(matrix: &_touchMatrix, content: _contentRect); + } + + public var touchMatrix: CGAffineTransform + { + return _touchMatrix; + } + + // MARK: - Boundaries Check + + public func isInBoundsX(x: CGFloat) -> Bool + { + if (isInBoundsLeft(x) && isInBoundsRight(x)) + { + return true; + } + else + { + return false; + } + } + + public func isInBoundsY(y: CGFloat) -> Bool + { + if (isInBoundsTop(y) && isInBoundsBottom(y)) + { + return true; + } + else + { + return false; + } + } + + public func isInBounds(#x: CGFloat, y: CGFloat) -> Bool + { + if (isInBoundsX(x) && isInBoundsY(y)) + { + return true; + } + else + { + return false; + } + } + + public func isInBoundsLeft(x: CGFloat) -> Bool + { + return _contentRect.origin.x <= x ? true : false; + } + + public func isInBoundsRight(x: CGFloat) -> Bool + { + return (_contentRect.origin.x + _contentRect.size.width) >= x ? true : false; + } + + public func isInBoundsTop(y: CGFloat) -> Bool + { + return _contentRect.origin.y <= y ? true : false; + } + + public func isInBoundsBottom(y: CGFloat) -> Bool + { + return (_contentRect.origin.y + _contentRect.size.height) >= y ? true : false; + } + + /// returns the current x-scale factor + public var scaleX: CGFloat + { + return _scaleX; + } + + /// returns the current y-scale factor + public var scaleY: CGFloat + { + return _scaleY; + } + + /// if the chart is fully zoomed out, return true + public var isFullyZoomedOut: Bool + { + if (isFullyZoomedOutX && isFullyZoomedOutY) + { + return true; + } + else + { + return false; + } + } + + /// Returns true if the chart is fully zoomed out on it's y-axis (vertical). + public var isFullyZoomedOutY: Bool + { + if (_scaleY > _minScaleY || _minScaleY > 1.0) + { + return false; + } + else + { + return true; + } + } + + /// Returns true if the chart is fully zoomed out on it's x-axis (horizontal). + public var isFullyZoomedOutX: Bool + { + if (_scaleX > _minScaleX || _minScaleX > 1.0) + { + return false; + } + else + { + return true; + } + } + + /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the x-axis. + public func setDragOffsetX(offset: CGFloat) + { + _transOffsetX = offset; + } + + /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the y-axis. + public func setDragOffsetY(offset: CGFloat) + { + _transOffsetY = offset; + } + + /// Returns true if both drag offsets (x and y) are zero or smaller. + public var hasNoDragOffset: Bool + { + return _transOffsetX <= 0.0 && _transOffsetY <= 0.0 ? true : false; + } +} \ No newline at end of file diff --git a/Charts/Supporting Files/Charts.h b/Charts/Supporting Files/Charts.h new file mode 100755 index 0000000000..eb089f6837 --- /dev/null +++ b/Charts/Supporting Files/Charts.h @@ -0,0 +1,25 @@ +// +// Charts.h +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import + +//! Project version number for Charts. +FOUNDATION_EXPORT double ChartsVersionNumber; + +//! Project version string for Charts. +FOUNDATION_EXPORT const unsigned char ChartsVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Charts/Supporting Files/Info.plist b/Charts/Supporting Files/Info.plist new file mode 100644 index 0000000000..4b3eb0f2dc --- /dev/null +++ b/Charts/Supporting Files/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.dcg.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/ChartsDemo/ChartsDemo.xcodeproj/project.pbxproj b/ChartsDemo/ChartsDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..842f943663 --- /dev/null +++ b/ChartsDemo/ChartsDemo.xcodeproj/project.pbxproj @@ -0,0 +1,582 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5B0CC7851ABB875400665592 /* PieChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CC7831ABB875400665592 /* PieChartViewController.m */; }; + 5B0CC7861ABB875400665592 /* PieChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B0CC7841ABB875400665592 /* PieChartViewController.xib */; }; + 5B4316271AB8D8AE0009FCAA /* Icon-29@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B43161F1AB8D8AE0009FCAA /* Icon-29@2x.png */; }; + 5B4316281AB8D8AE0009FCAA /* Icon-29@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316201AB8D8AE0009FCAA /* Icon-29@3x.png */; }; + 5B4316291AB8D8AE0009FCAA /* Icon-40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316211AB8D8AE0009FCAA /* Icon-40@2x.png */; }; + 5B43162A1AB8D8AE0009FCAA /* Icon-40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316221AB8D8AE0009FCAA /* Icon-40@3x.png */; }; + 5B43162B1AB8D8AE0009FCAA /* Icon-60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316231AB8D8AE0009FCAA /* Icon-60@2x.png */; }; + 5B43162C1AB8D8AE0009FCAA /* Icon-60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316241AB8D8AE0009FCAA /* Icon-60@3x.png */; }; + 5B43162D1AB8D8AE0009FCAA /* iTunesArtwork in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316251AB8D8AE0009FCAA /* iTunesArtwork */; }; + 5B43162E1AB8D8AE0009FCAA /* iTunesArtwork@2x in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316261AB8D8AE0009FCAA /* iTunesArtwork@2x */; }; + 5B4316351AB8D8B70009FCAA /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316311AB8D8B70009FCAA /* Default-568h@2x.png */; }; + 5B4316361AB8D8B70009FCAA /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316321AB8D8B70009FCAA /* Default-667h@2x.png */; }; + 5B4316371AB8D8B70009FCAA /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316331AB8D8B70009FCAA /* Default-736h@3x.png */; }; + 5B4316381AB8D8B70009FCAA /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B4316341AB8D8B70009FCAA /* Default@2x.png */; }; + 5B57BBB51A9B26AA0036A6CC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B57BBB41A9B26AA0036A6CC /* main.m */; }; + 5B57BBB81A9B26AA0036A6CC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B57BBB71A9B26AA0036A6CC /* AppDelegate.m */; }; + 5B57BBBB1A9B26AA0036A6CC /* DemoListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B57BBBA1A9B26AA0036A6CC /* DemoListViewController.m */; }; + 5B8EAF241AB3271B009697AA /* DemoListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B8EAF231AB3271B009697AA /* DemoListViewController.xib */; }; + 5B8EAF281AB32CF5009697AA /* DemoBaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B8EAF261AB32CF5009697AA /* DemoBaseViewController.m */; }; + 5B8EAF301AB32E15009697AA /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5B8EAF2F1AB32E15009697AA /* Images.xcassets */; }; + 5B8EAF3D1AB32F27009697AA /* Charts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B8EAF371AB32EA1009697AA /* Charts.framework */; }; + 5BD47E5B1ABB0263008FCEC6 /* BalloonMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD47E5A1ABB0263008FCEC6 /* BalloonMarker.swift */; }; + 5BD47E601ABB3C91008FCEC6 /* LineChart2ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BD47E5E1ABB3C91008FCEC6 /* LineChart2ViewController.m */; }; + 5BD47E611ABB3C91008FCEC6 /* LineChart2ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BD47E5F1ABB3C91008FCEC6 /* LineChart2ViewController.xib */; }; + 5BD47E651ABB424E008FCEC6 /* BarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BD47E631ABB424E008FCEC6 /* BarChartViewController.m */; }; + 5BD47E661ABB424E008FCEC6 /* BarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BD47E641ABB424E008FCEC6 /* BarChartViewController.xib */; }; + 5BD8F0741AB89CE500566E05 /* LineChart1ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BD8F0721AB89CE500566E05 /* LineChart1ViewController.m */; }; + 5BD8F0751AB89CE500566E05 /* LineChart1ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BD8F0731AB89CE500566E05 /* LineChart1ViewController.xib */; }; + 5BDEDC411ABB7F73007D3A60 /* HorizontalBarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDEDC3F1ABB7F73007D3A60 /* HorizontalBarChartViewController.m */; }; + 5BDEDC421ABB7F73007D3A60 /* HorizontalBarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BDEDC401ABB7F73007D3A60 /* HorizontalBarChartViewController.xib */; }; + 5BDEDC471ABB871E007D3A60 /* CombinedChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BDEDC451ABB871E007D3A60 /* CombinedChartViewController.m */; }; + 5BDEDC481ABB871E007D3A60 /* CombinedChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BDEDC461ABB871E007D3A60 /* CombinedChartViewController.xib */; }; + 5BEAED121ABBFB2B0013F194 /* AnotherBarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED101ABBFB2B0013F194 /* AnotherBarChartViewController.m */; }; + 5BEAED131ABBFB2B0013F194 /* AnotherBarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED111ABBFB2B0013F194 /* AnotherBarChartViewController.xib */; }; + 5BEAED1B1ABBFB340013F194 /* ScatterChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED161ABBFB340013F194 /* ScatterChartViewController.m */; }; + 5BEAED1C1ABBFB340013F194 /* ScatterChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED171ABBFB340013F194 /* ScatterChartViewController.xib */; }; + 5BEAED1D1ABBFB340013F194 /* StackedBarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED191ABBFB340013F194 /* StackedBarChartViewController.m */; }; + 5BEAED1E1ABBFB340013F194 /* StackedBarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED1A1ABBFB340013F194 /* StackedBarChartViewController.xib */; }; + 5BEAED251ABC0BE20013F194 /* MultipleBarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED201ABC0BE20013F194 /* MultipleBarChartViewController.m */; }; + 5BEAED261ABC0BE20013F194 /* MultipleBarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED211ABC0BE20013F194 /* MultipleBarChartViewController.xib */; }; + 5BEAED271ABC0BE20013F194 /* MultipleLinesChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED231ABC0BE20013F194 /* MultipleLinesChartViewController.m */; }; + 5BEAED281ABC0BE20013F194 /* MultipleLinesChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED241ABC0BE20013F194 /* MultipleLinesChartViewController.xib */; }; + 5BEAED2C1ABC160F0013F194 /* CandleStickChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED2A1ABC160F0013F194 /* CandleStickChartViewController.m */; }; + 5BEAED2D1ABC160F0013F194 /* CandleStickChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED2B1ABC160F0013F194 /* CandleStickChartViewController.xib */; }; + 5BEAED311ABC18F00013F194 /* CubicLineChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED2F1ABC18F00013F194 /* CubicLineChartViewController.m */; }; + 5BEAED321ABC18F00013F194 /* CubicLineChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED301ABC18F00013F194 /* CubicLineChartViewController.xib */; }; + 5BEAED361ABC192F0013F194 /* RadarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED341ABC192F0013F194 /* RadarChartViewController.m */; }; + 5BEAED371ABC192F0013F194 /* RadarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED351ABC192F0013F194 /* RadarChartViewController.xib */; }; + 5BEAED3B1ABC199F0013F194 /* ColoredLineChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED391ABC199F0013F194 /* ColoredLineChartViewController.m */; }; + 5BEAED3C1ABC199F0013F194 /* ColoredLineChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED3A1ABC199F0013F194 /* ColoredLineChartViewController.xib */; }; + 5BEAED401ABC1AC60013F194 /* SinusBarChartViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5BEAED3E1ABC1AC60013F194 /* SinusBarChartViewController.m */; }; + 5BEAED411ABC1AC60013F194 /* SinusBarChartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5BEAED3F1ABC1AC60013F194 /* SinusBarChartViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 5B8EAF361AB32EA1009697AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5B8EAF321AB32EA0009697AA /* Charts.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 5BA8EC401A9D14DC00CE82E1; + remoteInfo = Charts; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 5B0CC7821ABB875400665592 /* PieChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PieChartViewController.h; sourceTree = ""; }; + 5B0CC7831ABB875400665592 /* PieChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PieChartViewController.m; sourceTree = ""; }; + 5B0CC7841ABB875400665592 /* PieChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PieChartViewController.xib; sourceTree = ""; }; + 5B43161F1AB8D8AE0009FCAA /* Icon-29@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-29@2x.png"; sourceTree = ""; }; + 5B4316201AB8D8AE0009FCAA /* Icon-29@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-29@3x.png"; sourceTree = ""; }; + 5B4316211AB8D8AE0009FCAA /* Icon-40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-40@2x.png"; sourceTree = ""; }; + 5B4316221AB8D8AE0009FCAA /* Icon-40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-40@3x.png"; sourceTree = ""; }; + 5B4316231AB8D8AE0009FCAA /* Icon-60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@2x.png"; sourceTree = ""; }; + 5B4316241AB8D8AE0009FCAA /* Icon-60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@3x.png"; sourceTree = ""; }; + 5B4316251AB8D8AE0009FCAA /* iTunesArtwork */ = {isa = PBXFileReference; lastKnownFileType = file; path = iTunesArtwork; sourceTree = ""; }; + 5B4316261AB8D8AE0009FCAA /* iTunesArtwork@2x */ = {isa = PBXFileReference; lastKnownFileType = file; path = "iTunesArtwork@2x"; sourceTree = ""; }; + 5B4316311AB8D8B70009FCAA /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + 5B4316321AB8D8B70009FCAA /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = ""; }; + 5B4316331AB8D8B70009FCAA /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = ""; }; + 5B4316341AB8D8B70009FCAA /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; + 5B57BBAF1A9B26AA0036A6CC /* ChartsDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChartsDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B57BBB31A9B26AA0036A6CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5B57BBB41A9B26AA0036A6CC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 5B57BBB61A9B26AA0036A6CC /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 5B57BBB71A9B26AA0036A6CC /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 5B57BBB91A9B26AA0036A6CC /* DemoListViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DemoListViewController.h; sourceTree = ""; }; + 5B57BBBA1A9B26AA0036A6CC /* DemoListViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DemoListViewController.m; sourceTree = ""; }; + 5B8EAF231AB3271B009697AA /* DemoListViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DemoListViewController.xib; sourceTree = ""; }; + 5B8EAF251AB32CF5009697AA /* DemoBaseViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DemoBaseViewController.h; sourceTree = ""; }; + 5B8EAF261AB32CF5009697AA /* DemoBaseViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DemoBaseViewController.m; sourceTree = ""; }; + 5B8EAF2F1AB32E15009697AA /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 5B8EAF321AB32EA0009697AA /* Charts.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Charts.xcodeproj; path = ../Charts/Charts.xcodeproj; sourceTree = ""; }; + 5BD47E5A1ABB0263008FCEC6 /* BalloonMarker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BalloonMarker.swift; sourceTree = ""; }; + 5BD47E5C1ABB0273008FCEC6 /* ChartsDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ChartsDemo-Bridging-Header.h"; sourceTree = ""; }; + 5BD47E5D1ABB3C91008FCEC6 /* LineChart2ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LineChart2ViewController.h; sourceTree = ""; }; + 5BD47E5E1ABB3C91008FCEC6 /* LineChart2ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LineChart2ViewController.m; sourceTree = ""; }; + 5BD47E5F1ABB3C91008FCEC6 /* LineChart2ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LineChart2ViewController.xib; sourceTree = ""; }; + 5BD47E621ABB424E008FCEC6 /* BarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BarChartViewController.h; sourceTree = ""; }; + 5BD47E631ABB424E008FCEC6 /* BarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BarChartViewController.m; sourceTree = ""; }; + 5BD47E641ABB424E008FCEC6 /* BarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BarChartViewController.xib; sourceTree = ""; }; + 5BD8F0711AB89CE500566E05 /* LineChart1ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LineChart1ViewController.h; sourceTree = ""; }; + 5BD8F0721AB89CE500566E05 /* LineChart1ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LineChart1ViewController.m; sourceTree = ""; }; + 5BD8F0731AB89CE500566E05 /* LineChart1ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LineChart1ViewController.xib; sourceTree = ""; }; + 5BDEDC3E1ABB7F73007D3A60 /* HorizontalBarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HorizontalBarChartViewController.h; sourceTree = ""; }; + 5BDEDC3F1ABB7F73007D3A60 /* HorizontalBarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HorizontalBarChartViewController.m; sourceTree = ""; }; + 5BDEDC401ABB7F73007D3A60 /* HorizontalBarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HorizontalBarChartViewController.xib; sourceTree = ""; }; + 5BDEDC441ABB871E007D3A60 /* CombinedChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CombinedChartViewController.h; sourceTree = ""; }; + 5BDEDC451ABB871E007D3A60 /* CombinedChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CombinedChartViewController.m; sourceTree = ""; }; + 5BDEDC461ABB871E007D3A60 /* CombinedChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CombinedChartViewController.xib; sourceTree = ""; }; + 5BEAED0F1ABBFB2B0013F194 /* AnotherBarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnotherBarChartViewController.h; sourceTree = ""; }; + 5BEAED101ABBFB2B0013F194 /* AnotherBarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnotherBarChartViewController.m; sourceTree = ""; }; + 5BEAED111ABBFB2B0013F194 /* AnotherBarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AnotherBarChartViewController.xib; sourceTree = ""; }; + 5BEAED151ABBFB340013F194 /* ScatterChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScatterChartViewController.h; sourceTree = ""; }; + 5BEAED161ABBFB340013F194 /* ScatterChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScatterChartViewController.m; sourceTree = ""; }; + 5BEAED171ABBFB340013F194 /* ScatterChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ScatterChartViewController.xib; sourceTree = ""; }; + 5BEAED181ABBFB340013F194 /* StackedBarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StackedBarChartViewController.h; sourceTree = ""; }; + 5BEAED191ABBFB340013F194 /* StackedBarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StackedBarChartViewController.m; sourceTree = ""; }; + 5BEAED1A1ABBFB340013F194 /* StackedBarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StackedBarChartViewController.xib; sourceTree = ""; }; + 5BEAED1F1ABC0BE20013F194 /* MultipleBarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipleBarChartViewController.h; sourceTree = ""; }; + 5BEAED201ABC0BE20013F194 /* MultipleBarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipleBarChartViewController.m; sourceTree = ""; }; + 5BEAED211ABC0BE20013F194 /* MultipleBarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MultipleBarChartViewController.xib; sourceTree = ""; }; + 5BEAED221ABC0BE20013F194 /* MultipleLinesChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipleLinesChartViewController.h; sourceTree = ""; }; + 5BEAED231ABC0BE20013F194 /* MultipleLinesChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipleLinesChartViewController.m; sourceTree = ""; }; + 5BEAED241ABC0BE20013F194 /* MultipleLinesChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MultipleLinesChartViewController.xib; sourceTree = ""; }; + 5BEAED291ABC160F0013F194 /* CandleStickChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CandleStickChartViewController.h; sourceTree = ""; }; + 5BEAED2A1ABC160F0013F194 /* CandleStickChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CandleStickChartViewController.m; sourceTree = ""; }; + 5BEAED2B1ABC160F0013F194 /* CandleStickChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CandleStickChartViewController.xib; sourceTree = ""; }; + 5BEAED2E1ABC18F00013F194 /* CubicLineChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CubicLineChartViewController.h; sourceTree = ""; }; + 5BEAED2F1ABC18F00013F194 /* CubicLineChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CubicLineChartViewController.m; sourceTree = ""; }; + 5BEAED301ABC18F00013F194 /* CubicLineChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CubicLineChartViewController.xib; sourceTree = ""; }; + 5BEAED331ABC192F0013F194 /* RadarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RadarChartViewController.h; sourceTree = ""; }; + 5BEAED341ABC192F0013F194 /* RadarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RadarChartViewController.m; sourceTree = ""; }; + 5BEAED351ABC192F0013F194 /* RadarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RadarChartViewController.xib; sourceTree = ""; }; + 5BEAED381ABC199F0013F194 /* ColoredLineChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColoredLineChartViewController.h; sourceTree = ""; }; + 5BEAED391ABC199F0013F194 /* ColoredLineChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ColoredLineChartViewController.m; sourceTree = ""; }; + 5BEAED3A1ABC199F0013F194 /* ColoredLineChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ColoredLineChartViewController.xib; sourceTree = ""; }; + 5BEAED3D1ABC1AC60013F194 /* SinusBarChartViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SinusBarChartViewController.h; sourceTree = ""; }; + 5BEAED3E1ABC1AC60013F194 /* SinusBarChartViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SinusBarChartViewController.m; sourceTree = ""; }; + 5BEAED3F1ABC1AC60013F194 /* SinusBarChartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SinusBarChartViewController.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5B57BBAC1A9B26AA0036A6CC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B8EAF3D1AB32F27009697AA /* Charts.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5B43161E1AB8D8AE0009FCAA /* app-icon */ = { + isa = PBXGroup; + children = ( + 5B43161F1AB8D8AE0009FCAA /* Icon-29@2x.png */, + 5B4316201AB8D8AE0009FCAA /* Icon-29@3x.png */, + 5B4316211AB8D8AE0009FCAA /* Icon-40@2x.png */, + 5B4316221AB8D8AE0009FCAA /* Icon-40@3x.png */, + 5B4316231AB8D8AE0009FCAA /* Icon-60@2x.png */, + 5B4316241AB8D8AE0009FCAA /* Icon-60@3x.png */, + 5B4316251AB8D8AE0009FCAA /* iTunesArtwork */, + 5B4316261AB8D8AE0009FCAA /* iTunesArtwork@2x */, + ); + path = "app-icon"; + sourceTree = ""; + }; + 5B4316301AB8D8B70009FCAA /* launch-image */ = { + isa = PBXGroup; + children = ( + 5B4316311AB8D8B70009FCAA /* Default-568h@2x.png */, + 5B4316321AB8D8B70009FCAA /* Default-667h@2x.png */, + 5B4316331AB8D8B70009FCAA /* Default-736h@3x.png */, + 5B4316341AB8D8B70009FCAA /* Default@2x.png */, + ); + path = "launch-image"; + sourceTree = ""; + }; + 5B57BBA61A9B26AA0036A6CC = { + isa = PBXGroup; + children = ( + 5B57BBB11A9B26AA0036A6CC /* Classes */, + 5B8EAF2E1AB32E15009697AA /* Resources */, + 5B57BBB21A9B26AA0036A6CC /* Supporting Files */, + 5B8EAF321AB32EA0009697AA /* Charts.xcodeproj */, + 5B57BBB01A9B26AA0036A6CC /* Products */, + ); + sourceTree = ""; + }; + 5B57BBB01A9B26AA0036A6CC /* Products */ = { + isa = PBXGroup; + children = ( + 5B57BBAF1A9B26AA0036A6CC /* ChartsDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + 5B57BBB11A9B26AA0036A6CC /* Classes */ = { + isa = PBXGroup; + children = ( + 5B57BBB61A9B26AA0036A6CC /* AppDelegate.h */, + 5B57BBB71A9B26AA0036A6CC /* AppDelegate.m */, + 5B57BBB91A9B26AA0036A6CC /* DemoListViewController.h */, + 5B57BBBA1A9B26AA0036A6CC /* DemoListViewController.m */, + 5B8EAF231AB3271B009697AA /* DemoListViewController.xib */, + 5B8EAF251AB32CF5009697AA /* DemoBaseViewController.h */, + 5B8EAF261AB32CF5009697AA /* DemoBaseViewController.m */, + 5BD47E541ABB0177008FCEC6 /* Components */, + 5BD8F06F1AB89C7100566E05 /* Demos */, + ); + path = Classes; + sourceTree = ""; + }; + 5B57BBB21A9B26AA0036A6CC /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 5B57BBB31A9B26AA0036A6CC /* Info.plist */, + 5B57BBB41A9B26AA0036A6CC /* main.m */, + 5BD47E5C1ABB0273008FCEC6 /* ChartsDemo-Bridging-Header.h */, + ); + path = "Supporting Files"; + sourceTree = ""; + }; + 5B8EAF2E1AB32E15009697AA /* Resources */ = { + isa = PBXGroup; + children = ( + 5B8EAF2F1AB32E15009697AA /* Images.xcassets */, + 5B43161E1AB8D8AE0009FCAA /* app-icon */, + 5B4316301AB8D8B70009FCAA /* launch-image */, + ); + path = Resources; + sourceTree = ""; + }; + 5B8EAF331AB32EA0009697AA /* Products */ = { + isa = PBXGroup; + children = ( + 5B8EAF371AB32EA1009697AA /* Charts.framework */, + ); + name = Products; + sourceTree = ""; + }; + 5BD47E541ABB0177008FCEC6 /* Components */ = { + isa = PBXGroup; + children = ( + 5BD47E5A1ABB0263008FCEC6 /* BalloonMarker.swift */, + ); + path = Components; + sourceTree = ""; + }; + 5BD8F06F1AB89C7100566E05 /* Demos */ = { + isa = PBXGroup; + children = ( + 5BEAED0F1ABBFB2B0013F194 /* AnotherBarChartViewController.h */, + 5BEAED101ABBFB2B0013F194 /* AnotherBarChartViewController.m */, + 5BEAED111ABBFB2B0013F194 /* AnotherBarChartViewController.xib */, + 5BD47E621ABB424E008FCEC6 /* BarChartViewController.h */, + 5BD47E631ABB424E008FCEC6 /* BarChartViewController.m */, + 5BD47E641ABB424E008FCEC6 /* BarChartViewController.xib */, + 5BEAED291ABC160F0013F194 /* CandleStickChartViewController.h */, + 5BEAED2A1ABC160F0013F194 /* CandleStickChartViewController.m */, + 5BEAED2B1ABC160F0013F194 /* CandleStickChartViewController.xib */, + 5BEAED381ABC199F0013F194 /* ColoredLineChartViewController.h */, + 5BEAED391ABC199F0013F194 /* ColoredLineChartViewController.m */, + 5BEAED3A1ABC199F0013F194 /* ColoredLineChartViewController.xib */, + 5BDEDC441ABB871E007D3A60 /* CombinedChartViewController.h */, + 5BDEDC451ABB871E007D3A60 /* CombinedChartViewController.m */, + 5BDEDC461ABB871E007D3A60 /* CombinedChartViewController.xib */, + 5BEAED2E1ABC18F00013F194 /* CubicLineChartViewController.h */, + 5BEAED2F1ABC18F00013F194 /* CubicLineChartViewController.m */, + 5BEAED301ABC18F00013F194 /* CubicLineChartViewController.xib */, + 5BDEDC3E1ABB7F73007D3A60 /* HorizontalBarChartViewController.h */, + 5BDEDC3F1ABB7F73007D3A60 /* HorizontalBarChartViewController.m */, + 5BDEDC401ABB7F73007D3A60 /* HorizontalBarChartViewController.xib */, + 5BD8F0711AB89CE500566E05 /* LineChart1ViewController.h */, + 5BD8F0721AB89CE500566E05 /* LineChart1ViewController.m */, + 5BD8F0731AB89CE500566E05 /* LineChart1ViewController.xib */, + 5BD47E5D1ABB3C91008FCEC6 /* LineChart2ViewController.h */, + 5BD47E5E1ABB3C91008FCEC6 /* LineChart2ViewController.m */, + 5BD47E5F1ABB3C91008FCEC6 /* LineChart2ViewController.xib */, + 5BEAED1F1ABC0BE20013F194 /* MultipleBarChartViewController.h */, + 5BEAED201ABC0BE20013F194 /* MultipleBarChartViewController.m */, + 5BEAED211ABC0BE20013F194 /* MultipleBarChartViewController.xib */, + 5BEAED221ABC0BE20013F194 /* MultipleLinesChartViewController.h */, + 5BEAED231ABC0BE20013F194 /* MultipleLinesChartViewController.m */, + 5BEAED241ABC0BE20013F194 /* MultipleLinesChartViewController.xib */, + 5B0CC7821ABB875400665592 /* PieChartViewController.h */, + 5B0CC7831ABB875400665592 /* PieChartViewController.m */, + 5B0CC7841ABB875400665592 /* PieChartViewController.xib */, + 5BEAED331ABC192F0013F194 /* RadarChartViewController.h */, + 5BEAED341ABC192F0013F194 /* RadarChartViewController.m */, + 5BEAED351ABC192F0013F194 /* RadarChartViewController.xib */, + 5BEAED151ABBFB340013F194 /* ScatterChartViewController.h */, + 5BEAED161ABBFB340013F194 /* ScatterChartViewController.m */, + 5BEAED171ABBFB340013F194 /* ScatterChartViewController.xib */, + 5BEAED3D1ABC1AC60013F194 /* SinusBarChartViewController.h */, + 5BEAED3E1ABC1AC60013F194 /* SinusBarChartViewController.m */, + 5BEAED3F1ABC1AC60013F194 /* SinusBarChartViewController.xib */, + 5BEAED181ABBFB340013F194 /* StackedBarChartViewController.h */, + 5BEAED191ABBFB340013F194 /* StackedBarChartViewController.m */, + 5BEAED1A1ABBFB340013F194 /* StackedBarChartViewController.xib */, + ); + path = Demos; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5B57BBAE1A9B26AA0036A6CC /* ChartsDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5B57BBD21A9B26AA0036A6CC /* Build configuration list for PBXNativeTarget "ChartsDemo" */; + buildPhases = ( + 5B57BBAB1A9B26AA0036A6CC /* Sources */, + 5B57BBAC1A9B26AA0036A6CC /* Frameworks */, + 5B57BBAD1A9B26AA0036A6CC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ChartsDemo; + productName = chartstest; + productReference = 5B57BBAF1A9B26AA0036A6CC /* ChartsDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5B57BBA71A9B26AA0036A6CC /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = dcg; + TargetAttributes = { + 5B57BBAE1A9B26AA0036A6CC = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 5B57BBAA1A9B26AA0036A6CC /* Build configuration list for PBXProject "ChartsDemo" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5B57BBA61A9B26AA0036A6CC; + productRefGroup = 5B57BBB01A9B26AA0036A6CC /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 5B8EAF331AB32EA0009697AA /* Products */; + ProjectRef = 5B8EAF321AB32EA0009697AA /* Charts.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 5B57BBAE1A9B26AA0036A6CC /* ChartsDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 5B8EAF371AB32EA1009697AA /* Charts.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Charts.framework; + remoteRef = 5B8EAF361AB32EA1009697AA /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 5B57BBAD1A9B26AA0036A6CC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B8EAF301AB32E15009697AA /* Images.xcassets in Resources */, + 5B43162B1AB8D8AE0009FCAA /* Icon-60@2x.png in Resources */, + 5B43162E1AB8D8AE0009FCAA /* iTunesArtwork@2x in Resources */, + 5B4316281AB8D8AE0009FCAA /* Icon-29@3x.png in Resources */, + 5B4316361AB8D8B70009FCAA /* Default-667h@2x.png in Resources */, + 5BEAED2D1ABC160F0013F194 /* CandleStickChartViewController.xib in Resources */, + 5BD47E611ABB3C91008FCEC6 /* LineChart2ViewController.xib in Resources */, + 5BEAED131ABBFB2B0013F194 /* AnotherBarChartViewController.xib in Resources */, + 5BEAED411ABC1AC60013F194 /* SinusBarChartViewController.xib in Resources */, + 5BEAED371ABC192F0013F194 /* RadarChartViewController.xib in Resources */, + 5B4316351AB8D8B70009FCAA /* Default-568h@2x.png in Resources */, + 5B8EAF241AB3271B009697AA /* DemoListViewController.xib in Resources */, + 5BEAED261ABC0BE20013F194 /* MultipleBarChartViewController.xib in Resources */, + 5B43162A1AB8D8AE0009FCAA /* Icon-40@3x.png in Resources */, + 5BEAED3C1ABC199F0013F194 /* ColoredLineChartViewController.xib in Resources */, + 5BEAED321ABC18F00013F194 /* CubicLineChartViewController.xib in Resources */, + 5BEAED281ABC0BE20013F194 /* MultipleLinesChartViewController.xib in Resources */, + 5B4316381AB8D8B70009FCAA /* Default@2x.png in Resources */, + 5B0CC7861ABB875400665592 /* PieChartViewController.xib in Resources */, + 5BEAED1C1ABBFB340013F194 /* ScatterChartViewController.xib in Resources */, + 5BD8F0751AB89CE500566E05 /* LineChart1ViewController.xib in Resources */, + 5B43162D1AB8D8AE0009FCAA /* iTunesArtwork in Resources */, + 5BD47E661ABB424E008FCEC6 /* BarChartViewController.xib in Resources */, + 5BDEDC421ABB7F73007D3A60 /* HorizontalBarChartViewController.xib in Resources */, + 5BDEDC481ABB871E007D3A60 /* CombinedChartViewController.xib in Resources */, + 5B4316371AB8D8B70009FCAA /* Default-736h@3x.png in Resources */, + 5B4316291AB8D8AE0009FCAA /* Icon-40@2x.png in Resources */, + 5BEAED1E1ABBFB340013F194 /* StackedBarChartViewController.xib in Resources */, + 5B4316271AB8D8AE0009FCAA /* Icon-29@2x.png in Resources */, + 5B43162C1AB8D8AE0009FCAA /* Icon-60@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5B57BBAB1A9B26AA0036A6CC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5BEAED1B1ABBFB340013F194 /* ScatterChartViewController.m in Sources */, + 5B0CC7851ABB875400665592 /* PieChartViewController.m in Sources */, + 5B57BBBB1A9B26AA0036A6CC /* DemoListViewController.m in Sources */, + 5BD47E651ABB424E008FCEC6 /* BarChartViewController.m in Sources */, + 5BDEDC471ABB871E007D3A60 /* CombinedChartViewController.m in Sources */, + 5BD8F0741AB89CE500566E05 /* LineChart1ViewController.m in Sources */, + 5BEAED401ABC1AC60013F194 /* SinusBarChartViewController.m in Sources */, + 5BEAED251ABC0BE20013F194 /* MultipleBarChartViewController.m in Sources */, + 5B57BBB81A9B26AA0036A6CC /* AppDelegate.m in Sources */, + 5BD47E5B1ABB0263008FCEC6 /* BalloonMarker.swift in Sources */, + 5BEAED2C1ABC160F0013F194 /* CandleStickChartViewController.m in Sources */, + 5BEAED271ABC0BE20013F194 /* MultipleLinesChartViewController.m in Sources */, + 5B8EAF281AB32CF5009697AA /* DemoBaseViewController.m in Sources */, + 5BEAED3B1ABC199F0013F194 /* ColoredLineChartViewController.m in Sources */, + 5BDEDC411ABB7F73007D3A60 /* HorizontalBarChartViewController.m in Sources */, + 5BEAED121ABBFB2B0013F194 /* AnotherBarChartViewController.m in Sources */, + 5BEAED311ABC18F00013F194 /* CubicLineChartViewController.m in Sources */, + 5BEAED1D1ABBFB340013F194 /* StackedBarChartViewController.m in Sources */, + 5BD47E601ABB3C91008FCEC6 /* LineChart2ViewController.m in Sources */, + 5B57BBB51A9B26AA0036A6CC /* main.m in Sources */, + 5BEAED361ABC192F0013F194 /* RadarChartViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 5B57BBD01A9B26AA0036A6CC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 5B57BBD11A9B26AA0036A6CC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5B57BBD31A9B26AA0036A6CC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = "Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = ChartsDemo; + SWIFT_OBJC_BRIDGING_HEADER = "Supporting Files/ChartsDemo-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 5B57BBD41A9B26AA0036A6CC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + INFOPLIST_FILE = "Supporting Files/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = ChartsDemo; + SWIFT_OBJC_BRIDGING_HEADER = "Supporting Files/ChartsDemo-Bridging-Header.h"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5B57BBAA1A9B26AA0036A6CC /* Build configuration list for PBXProject "ChartsDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5B57BBD01A9B26AA0036A6CC /* Debug */, + 5B57BBD11A9B26AA0036A6CC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5B57BBD21A9B26AA0036A6CC /* Build configuration list for PBXNativeTarget "ChartsDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5B57BBD31A9B26AA0036A6CC /* Debug */, + 5B57BBD41A9B26AA0036A6CC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5B57BBA71A9B26AA0036A6CC /* Project object */; +} diff --git a/ChartsDemo/Classes/AppDelegate.h b/ChartsDemo/Classes/AppDelegate.h new file mode 100644 index 0000000000..60359e9915 --- /dev/null +++ b/ChartsDemo/Classes/AppDelegate.h @@ -0,0 +1,22 @@ +// +// AppDelegate.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 23/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/ChartsDemo/Classes/AppDelegate.m b/ChartsDemo/Classes/AppDelegate.m new file mode 100644 index 0000000000..60cd377691 --- /dev/null +++ b/ChartsDemo/Classes/AppDelegate.m @@ -0,0 +1,58 @@ +// +// AppDelegate.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 23/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "AppDelegate.h" +#import "DemoListViewController.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + + DemoListViewController *vc = [[DemoListViewController alloc] init]; + UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:vc]; + + _window.rootViewController = nvc; + [_window makeKeyAndVisible]; + + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/ChartsDemo/Classes/Components/BalloonMarker.swift b/ChartsDemo/Classes/Components/BalloonMarker.swift new file mode 100644 index 0000000000..46aed99a26 --- /dev/null +++ b/ChartsDemo/Classes/Components/BalloonMarker.swift @@ -0,0 +1,109 @@ +// +// BalloonMarker.swift +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 19/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation +import UIKit; +import Charts; + +public class BalloonMarker: ChartMarker +{ + public var color: UIColor!; + public var arrowSize = CGSize(width: 15, height: 11); + public var font: UIFont!; + public var insets = UIEdgeInsets(); + public var minimumSize = CGSize(); + + private var labelns: NSString!; + private var _labelSize: CGSize = CGSize(); + private var _size: CGSize = CGSize(); + private var _paragraphStyle: NSMutableParagraphStyle!; + + public init(color: UIColor, font: UIFont, insets: UIEdgeInsets) + { + super.init(); + + self.color = color; + self.font = font; + self.insets = insets; + + _paragraphStyle = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle; + _paragraphStyle.alignment = .Center; + } + + public override var size: CGSize { return _size; } + + public override func draw(#context: CGContext, point: CGPoint) + { + if (labelns === nil) + { + return; + } + + var rect = CGRect(origin: point, size: _size); + rect.origin.x -= _size.width / 2.0; + rect.origin.y -= _size.height; + + CGContextSaveGState(context); + + CGContextSetFillColorWithColor(context, color.CGColor); + CGContextBeginPath(context); + CGContextMoveToPoint(context, + rect.origin.x, + rect.origin.y); + CGContextAddLineToPoint(context, + rect.origin.x + rect.size.width, + rect.origin.y); + CGContextAddLineToPoint(context, + rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height - arrowSize.height); + CGContextAddLineToPoint(context, + rect.origin.x + (rect.size.width + arrowSize.width) / 2.0, + rect.origin.y + rect.size.height - arrowSize.height); + CGContextAddLineToPoint(context, + rect.origin.x + rect.size.width / 2.0, + rect.origin.y + rect.size.height); + CGContextAddLineToPoint(context, + rect.origin.x + (rect.size.width - arrowSize.width) / 2.0, + rect.origin.y + rect.size.height - arrowSize.height); + CGContextAddLineToPoint(context, + rect.origin.x, + rect.origin.y + rect.size.height - arrowSize.height); + CGContextAddLineToPoint(context, + rect.origin.x, + rect.origin.y); + CGContextFillPath(context); + + rect.origin.y += self.insets.top; + rect.size.height -= self.insets.top + self.insets.bottom; + + UIGraphicsPushContext(context); + + labelns.drawInRect(rect, withAttributes: [NSFontAttributeName: self.font, NSParagraphStyleAttributeName: _paragraphStyle]); + + UIGraphicsPopContext(); + + CGContextRestoreGState(context); + } + + public override func refreshContent(#entry: ChartDataEntry, dataSetIndex: Int) + { + var label = entry.value.description; + labelns = label as NSString; + + _labelSize = labelns.sizeWithAttributes([NSFontAttributeName: self.font]); + _size.width = _labelSize.width + self.insets.left + self.insets.right; + _size.height = _labelSize.height + self.insets.top + self.insets.bottom; + _size.width = max(minimumSize.width, _size.width); + _size.height = max(minimumSize.height, _size.height); + } +} \ No newline at end of file diff --git a/ChartsDemo/Classes/DemoBaseViewController.h b/ChartsDemo/Classes/DemoBaseViewController.h new file mode 100644 index 0000000000..4608320633 --- /dev/null +++ b/ChartsDemo/Classes/DemoBaseViewController.h @@ -0,0 +1,26 @@ +// +// DemoBaseViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 13/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import + +@interface DemoBaseViewController : UIViewController +{ +@protected + NSArray *months; + NSArray *parties; +} + +@property (nonatomic, strong) IBOutlet UIButton *optionsButton; +@property (nonatomic, strong) IBOutlet NSArray *options; + +@end diff --git a/ChartsDemo/Classes/DemoBaseViewController.m b/ChartsDemo/Classes/DemoBaseViewController.m new file mode 100644 index 0000000000..df6bebf70a --- /dev/null +++ b/ChartsDemo/Classes/DemoBaseViewController.m @@ -0,0 +1,183 @@ +// +// DemoBaseViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 13/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "DemoBaseViewController.h" + +@interface DemoBaseViewController () + +@property (nonatomic, strong) UITableView *optionsTableView; + +@end + +@implementation DemoBaseViewController + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + if (self) + { + [self initialize]; + } + return self; +} + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) + { + [self initialize]; + } + return self; +} + +- (void)initialize +{ + self.edgesForExtendedLayout = UIRectEdgeNone; + + months = @[ + @"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", + @"Oct", @"Nov", @"Dec" + ]; + + parties = @[ + @"Party A", @"Party B", @"Party C", @"Party D", @"Party E", @"Party F", + @"Party G", @"Party H", @"Party I", @"Party J", @"Party K", @"Party L", + @"Party M", @"Party N", @"Party O", @"Party P", @"Party Q", @"Party R", + @"Party S", @"Party T", @"Party U", @"Party V", @"Party W", @"Party X", + @"Party Y", @"Party Z" + ]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view from its nib. +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)optionTapped:(NSString *)key +{ + +} + +#pragma mark - Actions + +- (IBAction)optionsButtonTapped:(id)sender +{ + if (_optionsTableView) + { + [_optionsTableView removeFromSuperview]; + self.optionsTableView = nil; + return; + } + + self.optionsTableView = [[UITableView alloc] init]; + _optionsTableView.backgroundColor = [UIColor colorWithWhite:0.f alpha:0.9f]; + _optionsTableView.delegate = self; + _optionsTableView.dataSource = self; + + _optionsTableView.translatesAutoresizingMaskIntoConstraints = NO; + + NSMutableArray *constraints = [[NSMutableArray alloc] init]; + + [constraints addObject:[NSLayoutConstraint constraintWithItem:_optionsTableView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1.f constant:40.f]]; + + [constraints addObject:[NSLayoutConstraint constraintWithItem:_optionsTableView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:sender attribute:NSLayoutAttributeTrailing multiplier:1.f constant:0]]; + + [constraints addObject:[NSLayoutConstraint constraintWithItem:_optionsTableView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:sender attribute:NSLayoutAttributeBottom multiplier:1.f constant:5.f]]; + + [self.view addSubview:_optionsTableView]; + + [self.view addConstraints:constraints]; + + [_optionsTableView addConstraints:@[ + [NSLayoutConstraint constraintWithItem:_optionsTableView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.f constant:220.f] + ]]; +} + +#pragma mark - UITableViewDelegate, UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + if (tableView == _optionsTableView) + { + return 1; + } + + return 0; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (tableView == _optionsTableView) + { + return self.options.count; + } + + return 0; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (tableView == _optionsTableView) + { + return 40.0; + } + + return 44.0; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (tableView == _optionsTableView) + { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; + if (!cell) + { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"]; + cell.backgroundView = nil; + cell.backgroundColor = UIColor.clearColor; + cell.textLabel.textColor = UIColor.whiteColor; + } + + cell.textLabel.text = self.options[indexPath.row][@"label"]; + + return cell; + } + + return nil; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (tableView == _optionsTableView) + { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + + if (_optionsTableView) + { + [_optionsTableView removeFromSuperview]; + self.optionsTableView = nil; + } + + [self optionTapped:self.options[indexPath.row][@"key"]]; + } +} + +@end diff --git a/ChartsDemo/Classes/DemoListViewController.h b/ChartsDemo/Classes/DemoListViewController.h new file mode 100644 index 0000000000..433ee23f37 --- /dev/null +++ b/ChartsDemo/Classes/DemoListViewController.h @@ -0,0 +1,20 @@ +// +// DemoListViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 23/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import + +@interface DemoListViewController : UIViewController + + +@end + diff --git a/ChartsDemo/Classes/DemoListViewController.m b/ChartsDemo/Classes/DemoListViewController.m new file mode 100644 index 0000000000..09e7a64215 --- /dev/null +++ b/ChartsDemo/Classes/DemoListViewController.m @@ -0,0 +1,182 @@ +// +// DemoListViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 23/2/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "DemoListViewController.h" +#import "LineChart1ViewController.h" +#import "LineChart2ViewController.h" +#import "BarChartViewController.h" +#import "HorizontalBarChartViewController.h" +#import "CombinedChartViewController.h" +#import "PieChartViewController.h" +#import "ScatterChartViewController.h" +#import "StackedBarChartViewController.h" +#import "AnotherBarChartViewController.h" +#import "MultipleLinesChartViewController.h" +#import "MultipleBarChartViewController.h" +#import "CandleStickChartViewController.h" +#import "CubicLineChartViewController.h" +#import "RadarChartViewController.h" +#import "ColoredLineChartViewController.h" +#import "SinusBarChartViewController.h" + +@interface DemoListViewController () + +@property (nonatomic, strong) IBOutlet UITableView *tableView; +@property (nonatomic, strong) NSArray *itemDefs; +@end + +@implementation DemoListViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Charts Demonstration"; + + self.itemDefs = @[ + @{ + @"title": @"Line Chart", + @"subtitle": @"A simple demonstration of the linechart.", + @"class": LineChart1ViewController.class + }, + @{ + @"title": @"Line Chart (Dual YAxis)", + @"subtitle": @"Demonstration of the linechart with dual y-axis.", + @"class": LineChart2ViewController.class + }, + @{ + @"title": @"Bar Chart", + @"subtitle": @"A simple demonstration of the bar chart.", + @"class": BarChartViewController.class + }, + @{ + @"title": @"Horizontal Bar Chart", + @"subtitle": @"A simple demonstration of the horizontal bar chart.", + @"class": HorizontalBarChartViewController.class + }, + @{ + @"title": @"Combined Chart", + @"subtitle": @"Demonstrates how to create a combined chart (bar and line in this case).", + @"class": CombinedChartViewController.class + }, + @{ + @"title": @"Pie Chart", + @"subtitle": @"A simple demonstration of the pie chart.", + @"class": PieChartViewController.class + }, + @{ + @"title": @"Scatter Chart", + @"subtitle": @"A simple demonstration of the scatter chart.", + @"class": ScatterChartViewController.class + }, + @{ + @"title": @"Stacked Bar Chart", + @"subtitle": @"A simple demonstration of a bar chart with stacked bars.", + @"class": StackedBarChartViewController.class + }, + @{ + @"title": @"Another Bar Chart", + @"subtitle": @"Implementation of a BarChart that only shows values at the bottom.", + @"class": AnotherBarChartViewController.class + }, + @{ + @"title": @"Multiple Lines Chart", + @"subtitle": @"A line chart with multiple DataSet objects. One color per DataSet.", + @"class": MultipleLinesChartViewController.class + }, + @{ + @"title": @"Multiple Bars Chart", + @"subtitle": @"A bar chart with multiple DataSet objects. One multiple colors per DataSet.", + @"class": MultipleBarChartViewController.class + }, + @{ + @"title": @"Candle Stick Chart", + @"subtitle": @"Demonstrates usage of the CandleStickChart.", + @"class": CandleStickChartViewController.class + }, + @{ + @"title": @"Cubic Line Chart", + @"subtitle": @"Demonstrates cubic lines in a LineChart.", + @"class": CubicLineChartViewController.class + }, + @{ + @"title": @"Radar Chart", + @"subtitle": @"Demonstrates the use of a spider-web like (net) chart.", + @"class": RadarChartViewController.class + }, + @{ + @"title": @"Colored Line Chart", + @"subtitle": @"Shows a LineChart with different background and line color.", + @"class": ColoredLineChartViewController.class + }, + @{ + @"title": @"Sinus Bar Chart", + @"subtitle": @"A Bar Chart plotting the sinus function with 8.000 values.", + @"class": SinusBarChartViewController.class + }, + ]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +#pragma mark - UITableViewDataSource, UITableViewDelegate + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.itemDefs.count; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return 70.f; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSDictionary *def = self.itemDefs[indexPath.row]; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; + if (!cell) + { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"]; + } + + cell.textLabel.text = def[@"title"]; + cell.detailTextLabel.text = def[@"subtitle"]; + cell.detailTextLabel.numberOfLines = 0; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSDictionary *def = self.itemDefs[indexPath.row]; + + Class vcClass = def[@"class"]; + UIViewController *vc = [[vcClass alloc] init]; + + [self.navigationController pushViewController:vc animated:YES]; + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/ChartsDemo/Classes/DemoListViewController.xib b/ChartsDemo/Classes/DemoListViewController.xib new file mode 100644 index 0000000000..699067653c --- /dev/null +++ b/ChartsDemo/Classes/DemoListViewController.xib @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/AnotherBarChartViewController.h b/ChartsDemo/Classes/Demos/AnotherBarChartViewController.h new file mode 100644 index 0000000000..39d3a78912 --- /dev/null +++ b/ChartsDemo/Classes/Demos/AnotherBarChartViewController.h @@ -0,0 +1,20 @@ +// +// AnotherBarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface AnotherBarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/AnotherBarChartViewController.m b/ChartsDemo/Classes/Demos/AnotherBarChartViewController.m new file mode 100644 index 0000000000..96f4df0ae1 --- /dev/null +++ b/ChartsDemo/Classes/Demos/AnotherBarChartViewController.m @@ -0,0 +1,201 @@ +// +// AnotherBarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "AnotherBarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface AnotherBarChartViewController () + +@property (nonatomic, strong) IBOutlet BarChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation AnotherBarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Another Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleHighlightArrow", @"label": @"Toggle Highlight Arrow"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.maxVisibleValueCount = 60; + _chartView.pinchZoomEnabled = NO; + _chartView.drawBarShadowEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionBottom; + xAxis.spaceBetweenLabels = 0.f; + xAxis.drawGridLinesEnabled = NO; + + _chartView.leftAxis.drawGridLinesEnabled = NO; + _chartView.rightAxis.drawGridLinesEnabled = NO; + + _chartView.legend.enabled = NO; + + _sliderX.value = 9.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val = (float) (arc4random_uniform(mult)) + mult / 3.f; + [yVals addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + for (int i = 0; i < count; i++) + { + [xVals addObject:[@((int)((BarChartDataEntry *)yVals[i]).value) stringValue]]; + } + + BarChartDataSet *set1 = [[BarChartDataSet alloc] initWithYVals:yVals label:@"DataSet"]; + set1.colors = ChartColorTemplates.vordiplom; + set1.drawValuesEnabled = NO; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + + BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlightArrow"]) + { + _chartView.drawHighlightArrowEnabled = !_chartView.isDrawHighlightArrowEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/AnotherBarChartViewController.xib b/ChartsDemo/Classes/Demos/AnotherBarChartViewController.xib new file mode 100644 index 0000000000..8aaf4569f4 --- /dev/null +++ b/ChartsDemo/Classes/Demos/AnotherBarChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/BarChartViewController.h b/ChartsDemo/Classes/Demos/BarChartViewController.h new file mode 100644 index 0000000000..4c7d14c444 --- /dev/null +++ b/ChartsDemo/Classes/Demos/BarChartViewController.h @@ -0,0 +1,20 @@ +// +// BarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface BarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/BarChartViewController.m b/ChartsDemo/Classes/Demos/BarChartViewController.m new file mode 100644 index 0000000000..749c7beb57 --- /dev/null +++ b/ChartsDemo/Classes/Demos/BarChartViewController.m @@ -0,0 +1,221 @@ +// +// BarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "BarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface BarChartViewController () + +@property (nonatomic, strong) IBOutlet BarChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation BarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleHighlightArrow", @"label": @"Toggle Highlight Arrow"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.drawBarShadowEnabled = YES; + _chartView.drawValueAboveBarEnabled = YES; + + _chartView.maxVisibleValueCount = 60; + _chartView.pinchZoomEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionBottom; + xAxis.labelFont = [UIFont systemFontOfSize:10.f]; + xAxis.drawGridLinesEnabled = NO; + xAxis.spaceBetweenLabels = 2.f; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.labelFont = [UIFont systemFontOfSize:10.f]; + leftAxis.labelCount = 8; + leftAxis.valueFormatter = [[NSNumberFormatter alloc] init]; + leftAxis.valueFormatter.maximumFractionDigits = 1; + leftAxis.valueFormatter.negativeSuffix = @" $"; + leftAxis.valueFormatter.positiveSuffix = @" $"; + leftAxis.labelPosition = YAxisLabelPositionOutsideChart; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.drawGridLinesEnabled = NO; + rightAxis.labelFont = [UIFont systemFontOfSize:10.f]; + rightAxis.labelCount = 8; + rightAxis.valueFormatter = leftAxis.valueFormatter; + + _chartView.legend.position = ChartLegendPositionBelowChartLeft; + _chartView.legend.form = ChartLegendFormSquare; + _chartView.legend.formSize = 9.f; + _chartView.legend.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:11.f]; + _chartView.legend.xEntrySpace = 4.f; + + _sliderX.value = 11.f; + _sliderY.value = 50.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:months[i % 12]]; + } + + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val = (float) (arc4random_uniform(mult)); + [yVals addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + BarChartDataSet *set1 = [[BarChartDataSet alloc] initWithYVals:yVals label:@"DataSet"]; + set1.barSpace = 0.35f; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + + BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlightArrow"]) + { + _chartView.drawHighlightArrowEnabled = !_chartView.isDrawHighlightArrowEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/BarChartViewController.xib b/ChartsDemo/Classes/Demos/BarChartViewController.xib new file mode 100644 index 0000000000..7dd3ae83fe --- /dev/null +++ b/ChartsDemo/Classes/Demos/BarChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/CandleStickChartViewController.h b/ChartsDemo/Classes/Demos/CandleStickChartViewController.h new file mode 100644 index 0000000000..bd170b4061 --- /dev/null +++ b/ChartsDemo/Classes/Demos/CandleStickChartViewController.h @@ -0,0 +1,20 @@ +// +// CandleStickChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface CandleStickChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/CandleStickChartViewController.m b/ChartsDemo/Classes/Demos/CandleStickChartViewController.m new file mode 100644 index 0000000000..fc9cf6b0d6 --- /dev/null +++ b/ChartsDemo/Classes/Demos/CandleStickChartViewController.m @@ -0,0 +1,231 @@ +// +// CandleStickChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "CandleStickChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface CandleStickChartViewController () + +@property (nonatomic, strong) IBOutlet CandleStickChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation CandleStickChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Candle Stick Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.maxVisibleValueCount = 60; + _chartView.pinchZoomEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionBottom; + xAxis.spaceBetweenLabels = 2.f; + xAxis.drawGridLinesEnabled = NO; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.labelCount = 7; + leftAxis.drawGridLinesEnabled = NO; + leftAxis.drawAxisLineEnabled = NO; + leftAxis.startAtZeroEnabled = YES; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.enabled = NO; + + _chartView.legend.enabled = NO; + + _sliderX.value = 39.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i + 1990) stringValue]]; + } + + NSMutableArray *yVals1 = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val = (float) (arc4random_uniform(40)) + mult; + float high =(float) (arc4random_uniform(9)) + 8.f; + float low =(float) (arc4random_uniform(9)) + 8.f; + float open =(float) (arc4random_uniform(6)) + 1.f; + float close =(float) (arc4random_uniform(6)) + 1.f; + BOOL even = i % 2 == 0; + [yVals1 addObject:[[CandleChartDataEntry alloc] initWithXIndex:i shadowH:val + high shadowL:val - low open:even ? val + open : val - open close:even ? val - close : val + close]]; + } + + CandleChartDataSet *set1 = [[CandleChartDataSet alloc] initWithYVals:yVals1 label:@"Data Set"]; + set1.axisDependency = AxisDependencyLeft; + [set1 setColor:[UIColor colorWithWhite:80/255.f alpha:1.f]]; + + CandleChartData *data = [[CandleChartData alloc] initWithXVals:xVals dataSet:set1]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleFilled"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCircles"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCirclesEnabled = !set.isDrawCirclesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCubic"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCubicEnabled = !set.isDrawCubicEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/CandleStickChartViewController.xib b/ChartsDemo/Classes/Demos/CandleStickChartViewController.xib new file mode 100644 index 0000000000..b439e57460 --- /dev/null +++ b/ChartsDemo/Classes/Demos/CandleStickChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/ColoredLineChartViewController.h b/ChartsDemo/Classes/Demos/ColoredLineChartViewController.h new file mode 100644 index 0000000000..464e9bff28 --- /dev/null +++ b/ChartsDemo/Classes/Demos/ColoredLineChartViewController.h @@ -0,0 +1,20 @@ +// +// ColoredLineChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface ColoredLineChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/ColoredLineChartViewController.m b/ChartsDemo/Classes/Demos/ColoredLineChartViewController.m new file mode 100644 index 0000000000..32cea42313 --- /dev/null +++ b/ChartsDemo/Classes/Demos/ColoredLineChartViewController.m @@ -0,0 +1,123 @@ +// +// ColoredLineChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "ColoredLineChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface ColoredLineChartViewController () + +@property (nonatomic, strong) IBOutletCollection(LineChartView) NSArray *chartViews; + +@end + +@implementation ColoredLineChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Colored Line Chart"; + + LineChartData *data = [self dataWithCount:36 range:100]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:7.f]]; + + NSArray *colors = @[ + [UIColor colorWithRed:137/255.f green:230/255.f blue:81/255.f alpha:1.f], + [UIColor colorWithRed:240/255.f green:240/255.f blue:30/255.f alpha:1.f], + [UIColor colorWithRed:89/255.f green:199/255.f blue:250/255.f alpha:1.f], + [UIColor colorWithRed:250/255.f green:104/255.f blue:104/255.f alpha:1.f], + ]; + + for (int i = 0; i < _chartViews.count; i++) + { + [self setupChart:_chartViews[i] data:data color:colors[i % colors.count]]; + } +} + +- (void)setupChart:(LineChartView *)chart data:(LineChartData *)data color:(UIColor *)color +{ + chart.delegate = self; + chart.backgroundColor = color; + + chart.descriptionText = @""; + chart.noDataTextDescription = @"You need to provide data for the chart."; + + chart.drawGridBackgroundEnabled = NO; + chart.dragEnabled = YES; + [chart setScaleEnabled:YES]; + chart.pinchZoomEnabled = NO; + [chart setViewPortOffsetsWithLeft:10.f top:0.f right:10.f bottom:0.f]; + + ChartLegend *l = chart.legend; + l.form = ChartLegendFormCircle; + l.formSize = 6.f; + l.textColor = UIColor.whiteColor; + l.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + + chart.leftAxis.enabled = NO; + chart.rightAxis.enabled = NO; + chart.xAxis.enabled = NO; + + chart.data = data; + + [chart animateXWithDuration:2.5]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (LineChartData *)dataWithCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i % 12) stringValue]]; + } + + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float val = (float) (arc4random_uniform(range)) + 3; + [yVals addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + LineChartDataSet *set1 = [[LineChartDataSet alloc] initWithYVals:yVals label:@"DataSet 1"]; + + set1.lineWidth = 1.75f; + set1.circleRadius = 3.f; + [set1 setColor:UIColor.whiteColor]; + [set1 setCircleColor:UIColor.whiteColor]; + set1.highlightColor = UIColor.whiteColor; + set1.drawValuesEnabled = NO; + + return [[LineChartData alloc] initWithXVals:xVals dataSet:set1]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/ColoredLineChartViewController.xib b/ChartsDemo/Classes/Demos/ColoredLineChartViewController.xib new file mode 100644 index 0000000000..bc2fef68c4 --- /dev/null +++ b/ChartsDemo/Classes/Demos/ColoredLineChartViewController.xib @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/CombinedChartViewController.h b/ChartsDemo/Classes/Demos/CombinedChartViewController.h new file mode 100644 index 0000000000..d5b090b9df --- /dev/null +++ b/ChartsDemo/Classes/Demos/CombinedChartViewController.h @@ -0,0 +1,20 @@ +// +// CombinedChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface CombinedChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/CombinedChartViewController.m b/ChartsDemo/Classes/Demos/CombinedChartViewController.m new file mode 100644 index 0000000000..19b5a3735f --- /dev/null +++ b/ChartsDemo/Classes/Demos/CombinedChartViewController.m @@ -0,0 +1,166 @@ +// +// CombinedChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "CombinedChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface CombinedChartViewController () + +@property (nonatomic, strong) IBOutlet CombinedChartView *chartView; + +@end + +@implementation CombinedChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Combined Chart"; + + self.options = @[ + @{@"key": @"toggleLineValues", @"label": @"Toggle Line Values"}, + @{@"key": @"toggleBarValues", @"label": @"Toggle Bar Values"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.drawGridBackgroundEnabled = NO; + _chartView.drawBarShadowEnabled = NO; + + _chartView.drawOrder = @[@(DrawOrderBar), @(DrawOrderLine)]; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.drawGridLinesEnabled = NO; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.drawGridLinesEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionBothSided; + + CombinedChartData *data = [[CombinedChartData alloc] initWithXVals:months]; + data.lineData = [self generateLineData]; + data.barData = [self generateBarData]; + + _chartView.data = data; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleLineValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + if ([set isKindOfClass:LineChartDataSet.class]) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleBarValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + if ([set isKindOfClass:BarChartDataSet.class]) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } +} + +- (LineChartData *)generateLineData +{ + LineChartData *d = [[LineChartData alloc] init]; + + NSMutableArray *entries = [[NSMutableArray alloc] init]; + + for (int index = 0; index < 12; index++) + { + [entries addObject:[[ChartDataEntry alloc] initWithValue:(arc4random_uniform(15) + 10) xIndex:index]]; + } + + LineChartDataSet *set = [[LineChartDataSet alloc] initWithYVals:entries label:@"Line DataSet"]; + [set setColor:[UIColor colorWithRed:240/255.f green:238/255.f blue:70/255.f alpha:1.f]]; + set.lineWidth = 2.5f; + [set setCircleColor:[UIColor colorWithRed:240/255.f green:238/255.f blue:70/255.f alpha:1.f]]; + set.fillColor = [UIColor colorWithRed:240/255.f green:238/255.f blue:70/255.f alpha:1.f]; + set.drawCubicEnabled = YES; + set.drawValuesEnabled = YES; + set.valueFont = [UIFont systemFontOfSize:10.f]; + set.valueTextColor = [UIColor colorWithRed:240/255.f green:238/255.f blue:70/255.f alpha:1.f]; + + set.axisDependency = AxisDependencyLeft; + + [d addDataSet:set]; + + return d; +} + +- (BarChartData *)generateBarData +{ + BarChartData *d = [[BarChartData alloc] init]; + + NSMutableArray *entries = [[NSMutableArray alloc] init]; + + for (int index = 0; index < 12; index++) + { + [entries addObject:[[BarChartDataEntry alloc] initWithValue:(arc4random_uniform(15) + 30) xIndex:index]]; + } + + BarChartDataSet *set = [[BarChartDataSet alloc] initWithYVals:entries label:@"Bar DataSet"]; + [set setColor:[UIColor colorWithRed:60/255.f green:220/255.f blue:78/255.f alpha:1.f]]; + set.valueTextColor = [UIColor colorWithRed:60/255.f green:220/255.f blue:78/255.f alpha:1.f]; + set.valueFont = [UIFont systemFontOfSize:10.f]; + + set.axisDependency = AxisDependencyLeft; + + [d addDataSet:set]; + + return d; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/CombinedChartViewController.xib b/ChartsDemo/Classes/Demos/CombinedChartViewController.xib new file mode 100644 index 0000000000..e9cf01ea97 --- /dev/null +++ b/ChartsDemo/Classes/Demos/CombinedChartViewController.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/CubicLineChartViewController.h b/ChartsDemo/Classes/Demos/CubicLineChartViewController.h new file mode 100644 index 0000000000..d9388f0cbf --- /dev/null +++ b/ChartsDemo/Classes/Demos/CubicLineChartViewController.h @@ -0,0 +1,20 @@ +// +// CubicLineChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface CubicLineChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/CubicLineChartViewController.m b/ChartsDemo/Classes/Demos/CubicLineChartViewController.m new file mode 100644 index 0000000000..3ee88118dc --- /dev/null +++ b/ChartsDemo/Classes/Demos/CubicLineChartViewController.m @@ -0,0 +1,229 @@ +// +// CubicLineChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "CubicLineChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface CubicLineChartViewController () + +@property (nonatomic, strong) IBOutlet LineChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation CubicLineChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Cubic Line Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleFilled", @"label": @"Toggle Filled"}, + @{@"key": @"toggleCircles", @"label": @"Toggle Circles"}, + @{@"key": @"toggleCubic", @"label": @"Toggle Cubic"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.highlightEnabled = YES; + _chartView.dragEnabled = YES; + [_chartView setScaleEnabled:YES]; + _chartView.pinchZoomEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + _chartView.xAxis.enabled = NO; + _chartView.leftAxis.enabled = NO; + _chartView.rightAxis.enabled = NO; + _chartView.legend.enabled = NO; + + _sliderX.value = 44.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; + [_chartView animateXYWithDurationX:2.0 durationY:2.0]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i + 1990) stringValue]]; + } + + NSMutableArray *yVals1 = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val = (float) (arc4random_uniform(mult)) + 20; + [yVals1 addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + LineChartDataSet *set1 = [[LineChartDataSet alloc] initWithYVals:yVals1 label:@"DataSet 1"]; + set1.drawCubicEnabled = YES; + set1.cubicIntensity = 0.2f; + set1.drawCirclesEnabled = NO; + set1.lineWidth = 2.f; + set1.circleRadius = 5.f; + set1.highlightColor = [UIColor colorWithRed:244/255.f green:117/255.f blue:117/255.f alpha:1.f]; + [set1 setColor:[UIColor colorWithRed:104/255.f green:241/255.f blue:175/255.f alpha:1.f]]; + set1.fillColor = [UIColor colorWithRed:51/255.f green:181/255.f blue:229/255.f alpha:1.f]; + + LineChartData *data = [[LineChartData alloc] initWithXVals:xVals dataSet:set1]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:9.f]]; + [data setDrawValues:NO]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleFilled"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCircles"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCirclesEnabled = !set.isDrawCirclesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCubic"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCubicEnabled = !set.isDrawCubicEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/CubicLineChartViewController.xib b/ChartsDemo/Classes/Demos/CubicLineChartViewController.xib new file mode 100644 index 0000000000..ad4c1ed271 --- /dev/null +++ b/ChartsDemo/Classes/Demos/CubicLineChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.h b/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.h new file mode 100644 index 0000000000..d6d0ceefc4 --- /dev/null +++ b/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.h @@ -0,0 +1,20 @@ +// +// HorizontalBarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface HorizontalBarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.m b/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.m new file mode 100644 index 0000000000..edbc006f58 --- /dev/null +++ b/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.m @@ -0,0 +1,219 @@ +// +// HorizontalBarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "HorizontalBarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface HorizontalBarChartViewController () + +@property (nonatomic, strong) IBOutlet HorizontalBarChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation HorizontalBarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Horizontal Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleHighlightArrow", @"label": @"Toggle Highlight Arrow"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.drawBarShadowEnabled = NO; + _chartView.drawValueAboveBarEnabled = YES; + + _chartView.maxVisibleValueCount = 60; + _chartView.pinchZoomEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionBottom; + xAxis.labelFont = [UIFont systemFontOfSize:10.f]; + xAxis.drawAxisLineEnabled = YES; + xAxis.drawGridLinesEnabled = YES; + xAxis.gridLineWidth = .3f; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.labelFont = [UIFont systemFontOfSize:10.f]; + leftAxis.drawAxisLineEnabled = YES; + leftAxis.drawGridLinesEnabled = YES; + leftAxis.gridLineWidth = .3f; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.labelFont = [UIFont systemFontOfSize:10.f]; + rightAxis.drawAxisLineEnabled = YES; + rightAxis.drawGridLinesEnabled = NO; + + _chartView.legend.position = ChartLegendPositionBelowChartLeft; + _chartView.legend.form = ChartLegendFormSquare; + _chartView.legend.formSize = 8.f; + _chartView.legend.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:11.f]; + _chartView.legend.xEntrySpace = 4.f; + + _sliderX.value = 11.f; + _sliderY.value = 50.f; + [self slidersValueChanged:nil]; + [_chartView animateYWithDuration:2.5]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:months[i % 12]]; + } + + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val = (float) (arc4random_uniform(mult)); + [yVals addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + BarChartDataSet *set1 = [[BarChartDataSet alloc] initWithYVals:yVals label:@"DataSet"]; + set1.barSpace = 0.35f; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + + BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlightArrow"]) + { + _chartView.drawHighlightArrowEnabled = !_chartView.isDrawHighlightArrowEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.xib b/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.xib new file mode 100644 index 0000000000..9ce771c86f --- /dev/null +++ b/ChartsDemo/Classes/Demos/HorizontalBarChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/LineChart1ViewController.h b/ChartsDemo/Classes/Demos/LineChart1ViewController.h new file mode 100644 index 0000000000..8b0bab5361 --- /dev/null +++ b/ChartsDemo/Classes/Demos/LineChart1ViewController.h @@ -0,0 +1,20 @@ +// +// LineChart1ViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface LineChart1ViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/LineChart1ViewController.m b/ChartsDemo/Classes/Demos/LineChart1ViewController.m new file mode 100644 index 0000000000..4279433b32 --- /dev/null +++ b/ChartsDemo/Classes/Demos/LineChart1ViewController.m @@ -0,0 +1,255 @@ +// +// LineChart1ViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "LineChart1ViewController.h" +#import "ChartsDemo-Swift.h" + +@interface LineChart1ViewController () + +@property (nonatomic, strong) IBOutlet LineChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation LineChart1ViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Line Chart 1 Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleFilled", @"label": @"Toggle Filled"}, + @{@"key": @"toggleCircles", @"label": @"Toggle Circles"}, + @{@"key": @"toggleCubic", @"label": @"Toggle Cubic"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.highlightEnabled = YES; + _chartView.dragEnabled = YES; + [_chartView setScaleEnabled:YES]; + _chartView.pinchZoomEnabled = YES; + _chartView.highlightIndicatorEnabled = NO; + + BalloonMarker *marker = [[BalloonMarker alloc] initWithColor:[UIColor colorWithWhite:180/255. alpha:1.0] font:[UIFont systemFontOfSize:12.0] insets: UIEdgeInsetsMake(8.0, 8.0, 20.0, 8.0)]; + marker.minimumSize = CGSizeMake(80.f, 40.f); + _chartView.marker = marker; + + _chartView.legend.form = ChartLegendFormLine; + + _sliderX.value = 44.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; + [_chartView animateXWithDuration:2.5]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i) stringValue]]; + } + + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val = (float) (arc4random_uniform(mult)) + 3; + [yVals addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + LineChartDataSet *set1 = [[LineChartDataSet alloc] initWithYVals:yVals label:@"DataSet 1"]; + + set1.lineDashLengths = @[@5.f, @2.5f]; + [set1 setColor:UIColor.blackColor]; + [set1 setCircleColor:UIColor.blackColor]; + set1.lineWidth = 1.f; + set1.circleRadius = 3.f; + set1.drawCircleHoleEnabled = NO; + set1.valueFont = [UIFont systemFontOfSize:9.f]; + set1.fillAlpha = 65/255.f; + set1.fillColor = UIColor.blackColor; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + + LineChartData *data = [[LineChartData alloc] initWithXVals:xVals dataSets:dataSets]; + + ChartLimitLine *ll1 = [[ChartLimitLine alloc] initWithLimit:130.f label:@"Upper Limit"]; + ll1.lineWidth = 4.f; + ll1.lineDashLengths = @[@5.f, @5.f]; + ll1.labelPosition = LabelPositionRight; + ll1.valueFont = [UIFont systemFontOfSize:10.f]; + + ChartLimitLine *ll2 = [[ChartLimitLine alloc] initWithLimit:-30.f label:@"Lower Limit"]; + ll2.lineWidth = 4.f; + ll2.lineDashLengths = @[@5.f, @5.f]; + ll2.labelPosition = LabelPositionRight; + ll2.valueFont = [UIFont systemFontOfSize:10.f]; + + ChartYAxis *leftAxis = _chartView.leftAxis; + [leftAxis removeAllLimitLines]; + [leftAxis addLimitLine:ll1]; + [leftAxis addLimitLine:ll2]; + leftAxis.customAxisMax = 220.f; + leftAxis.customAxisMin = -50.f; + leftAxis.startAtZeroEnabled = NO; + + _chartView.rightAxis.enabled = NO; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleFilled"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCircles"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCirclesEnabled = !set.isDrawCirclesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCubic"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCubicEnabled = !set.isDrawCubicEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/LineChart1ViewController.xib b/ChartsDemo/Classes/Demos/LineChart1ViewController.xib new file mode 100644 index 0000000000..dc8bca5399 --- /dev/null +++ b/ChartsDemo/Classes/Demos/LineChart1ViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/LineChart2ViewController.h b/ChartsDemo/Classes/Demos/LineChart2ViewController.h new file mode 100644 index 0000000000..07faed62a5 --- /dev/null +++ b/ChartsDemo/Classes/Demos/LineChart2ViewController.h @@ -0,0 +1,20 @@ +// +// LineChart2ViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface LineChart2ViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/LineChart2ViewController.m b/ChartsDemo/Classes/Demos/LineChart2ViewController.m new file mode 100644 index 0000000000..7d68cf8e80 --- /dev/null +++ b/ChartsDemo/Classes/Demos/LineChart2ViewController.m @@ -0,0 +1,275 @@ +// +// LineChart2ViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "LineChart2ViewController.h" +#import "ChartsDemo-Swift.h" + +@interface LineChart2ViewController () + +@property (nonatomic, strong) IBOutlet LineChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation LineChart2ViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Line Chart 2 Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleFilled", @"label": @"Toggle Filled"}, + @{@"key": @"toggleCircles", @"label": @"Toggle Circles"}, + @{@"key": @"toggleCubic", @"label": @"Toggle Cubic"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.highlightEnabled = YES; + _chartView.dragEnabled = YES; + [_chartView setScaleEnabled:YES]; + _chartView.drawGridBackgroundEnabled = NO; + _chartView.pinchZoomEnabled = YES; + + _chartView.backgroundColor = [UIColor colorWithWhite:204/255.f alpha:1.f]; + + _chartView.legend.form = ChartLegendFormLine; + _chartView.legend.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:11.f]; + _chartView.legend.textColor = UIColor.whiteColor; + _chartView.legend.position = ChartLegendPositionBelowChartLeft; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelFont = [UIFont systemFontOfSize:12.f]; + xAxis.labelTextColor = UIColor.whiteColor; + xAxis.drawGridLinesEnabled = NO; + xAxis.drawAxisLineEnabled = NO; + xAxis.spaceBetweenLabels = 1.0; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.labelTextColor = [UIColor colorWithRed:51/255.f green:181/255.f blue:229/255.f alpha:1.f]; + leftAxis.customAxisMax = 200.f; + leftAxis.drawGridLinesEnabled = YES; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.labelTextColor = UIColor.redColor; + rightAxis.customAxisMax = 900.0; + rightAxis.startAtZeroEnabled = NO; + rightAxis.customAxisMin = -200.f; + rightAxis.drawGridLinesEnabled = NO; + + _sliderX.value = 19.f; + _sliderY.value = 30.f; + [self slidersValueChanged:nil]; + [_chartView animateXWithDuration:2.5]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i) stringValue]]; + } + + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = range / 2.f; + float val = (float) (arc4random_uniform(mult)) + 50; + [yVals addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + LineChartDataSet *set1 = [[LineChartDataSet alloc] initWithYVals:yVals label:@"DataSet 1"]; + set1.axisDependency = AxisDependencyLeft; + [set1 setColor:[UIColor colorWithRed:51/255.f green:181/255.f blue:229/255.f alpha:1.f]]; + [set1 setCircleColor:UIColor.whiteColor]; + set1.lineWidth = 2.f; + set1.circleRadius = 3.f; + set1.fillAlpha = 65/255.f; + set1.fillColor = [UIColor colorWithRed:51/255.f green:181/255.f blue:229/255.f alpha:1.f]; + set1.highlightColor = [UIColor colorWithRed:244/255.f green:117/255.f blue:117/255.f alpha:1.f]; + set1.drawCircleHoleEnabled = NO; + + NSMutableArray *yVals2 = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = range; + float val = (float) (arc4random_uniform(mult)) + 450; + [yVals2 addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + LineChartDataSet *set2 = [[LineChartDataSet alloc] initWithYVals:yVals2 label:@"DataSet 2"]; + set2.axisDependency = AxisDependencyRight; + [set2 setColor:UIColor.redColor]; + [set2 setCircleColor:UIColor.whiteColor]; + set2.lineWidth = 2.f; + set2.circleRadius = 3.f; + set2.fillAlpha = 65/255.f; + set2.fillColor = UIColor.redColor; + set2.highlightColor = [UIColor colorWithRed:244/255.f green:117/255.f blue:117/255.f alpha:1.f]; + set2.drawCircleHoleEnabled = NO; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set2]; + [dataSets addObject:set1]; + + LineChartData *data = [[LineChartData alloc] initWithXVals:xVals dataSets:dataSets]; + [data setValueTextColor:UIColor.whiteColor]; + [data setValueFont:[UIFont systemFontOfSize:9.f]]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleFilled"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCircles"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCirclesEnabled = !set.isDrawCirclesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCubic"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCubicEnabled = !set.isDrawCubicEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/LineChart2ViewController.xib b/ChartsDemo/Classes/Demos/LineChart2ViewController.xib new file mode 100644 index 0000000000..eb70c43187 --- /dev/null +++ b/ChartsDemo/Classes/Demos/LineChart2ViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/MultipleBarChartViewController.h b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.h new file mode 100644 index 0000000000..81ea95c62c --- /dev/null +++ b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.h @@ -0,0 +1,20 @@ +// +// MultipleBarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface MultipleBarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m new file mode 100644 index 0000000000..b1b21db6a1 --- /dev/null +++ b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m @@ -0,0 +1,229 @@ +// +// MultipleBarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "MultipleBarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface MultipleBarChartViewController () + +@property (nonatomic, strong) IBOutlet BarChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation MultipleBarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Multiple Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleHighlightArrow", @"label": @"Toggle Highlight Arrow"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.pinchZoomEnabled = NO; + _chartView.drawBarShadowEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + BalloonMarker *marker = [[BalloonMarker alloc] initWithColor:[UIColor colorWithWhite:180/255. alpha:1.0] font:[UIFont systemFontOfSize:12.0] insets: UIEdgeInsetsMake(8.0, 8.0, 20.0, 8.0)]; + marker.minimumSize = CGSizeMake(80.f, 40.f); + _chartView.marker = marker; + + ChartLegend *legend = _chartView.legend; + legend.position = ChartLegendPositionRightOfChartInside; + legend.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:11.f]; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + leftAxis.valueFormatter = [[NSNumberFormatter alloc] init]; + leftAxis.valueFormatter.maximumFractionDigits = 1; + leftAxis.drawGridLinesEnabled = NO; + leftAxis.spaceTop = 0.25f; + + _chartView.rightAxis.enabled = NO; + _chartView.valueFormatter = [[NSNumberFormatter alloc] init]; + _chartView.valueFormatter.maximumFractionDigits = 1; + + _sliderX.value = 9.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i + 1990) stringValue]]; + } + + NSMutableArray *yVals1 = [[NSMutableArray alloc] init]; + NSMutableArray *yVals2 = [[NSMutableArray alloc] init]; + NSMutableArray *yVals3 = [[NSMutableArray alloc] init]; + + float mult = range * 1000.f; + + for (int i = 0; i < count; i++) + { + float val = (float) (arc4random_uniform(mult) + 3.f); + [yVals1 addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; + + val = (float) (arc4random_uniform(mult) + 3.f); + [yVals2 addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; + + val = (float) (arc4random_uniform(mult) + 3.f); + [yVals3 addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + BarChartDataSet *set1 = [[BarChartDataSet alloc] initWithYVals:yVals1 label:@"Company A"]; + [set1 setColor:[UIColor colorWithRed:104/255.f green:241/255.f blue:175/255.f alpha:1.f]]; + BarChartDataSet *set2 = [[BarChartDataSet alloc] initWithYVals:yVals2 label:@"Company B"]; + [set2 setColor:[UIColor colorWithRed:164/255.f green:228/255.f blue:251/255.f alpha:1.f]]; + BarChartDataSet *set3 = [[BarChartDataSet alloc] initWithYVals:yVals3 label:@"Company C"]; + [set3 setColor:[UIColor colorWithRed:242/255.f green:247/255.f blue:158/255.f alpha:1.f]]; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + [dataSets addObject:set2]; + [dataSets addObject:set3]; + + BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; + data.groupSpace = 0.8f; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlightArrow"]) + { + _chartView.drawHighlightArrowEnabled = !_chartView.isDrawHighlightArrowEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/MultipleBarChartViewController.xib b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.xib new file mode 100644 index 0000000000..6d27d969b4 --- /dev/null +++ b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.h b/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.h new file mode 100644 index 0000000000..016fec4e76 --- /dev/null +++ b/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.h @@ -0,0 +1,20 @@ +// +// MultipleLinesChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface MultipleLinesChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.m b/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.m new file mode 100644 index 0000000000..875e877421 --- /dev/null +++ b/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.m @@ -0,0 +1,232 @@ +// +// MultipleLinesChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "MultipleLinesChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface MultipleLinesChartViewController () + +@property (nonatomic, strong) IBOutlet LineChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation MultipleLinesChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Multiple Lines Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleFilled", @"label": @"Toggle Filled"}, + @{@"key": @"toggleCircles", @"label": @"Toggle Circles"}, + @{@"key": @"toggleCubic", @"label": @"Toggle Cubic"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.drawGridBackgroundEnabled = NO; + _chartView.highlightEnabled = YES; + _chartView.dragEnabled = YES; + [_chartView setScaleEnabled:YES]; + _chartView.pinchZoomEnabled = NO; + + _chartView.legend.position = ChartLegendPositionRightOfChart; + + _sliderX.value = 19.f; + _sliderY.value = 10.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i) stringValue]]; + } + + NSArray *colors = @[ChartColorTemplates.vordiplom[0], ChartColorTemplates.vordiplom[1], ChartColorTemplates.vordiplom[2]]; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + + for (int z = 0; z < 3; z++) + { + NSMutableArray *values = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float val = (float) (arc4random_uniform(range) + 3); + [values addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + LineChartDataSet *d = [[LineChartDataSet alloc] initWithYVals:values label:[NSString stringWithFormat:@"DataSet %d", z + 1]]; + d.lineWidth = 2.5f; + d.circleRadius = 4.f; + + UIColor *color = colors[z % colors.count]; + [d setColor:color]; + [d setCircleColor:color]; + [dataSets addObject:d]; + } + + ((LineChartDataSet *)dataSets[0]).lineDashLengths = @[@5.f, @5.f]; + ((LineChartDataSet *)dataSets[0]).colors = ChartColorTemplates.vordiplom; + ((LineChartDataSet *)dataSets[0]).circleColors = ChartColorTemplates.vordiplom; + + LineChartData *data = [[LineChartData alloc] initWithXVals:xVals dataSets:dataSets]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:7.f]]; + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleFilled"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCircles"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCirclesEnabled = !set.isDrawCirclesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCubic"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCubicEnabled = !set.isDrawCubicEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.xib b/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.xib new file mode 100644 index 0000000000..00d50dfc9c --- /dev/null +++ b/ChartsDemo/Classes/Demos/MultipleLinesChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/PieChartViewController.h b/ChartsDemo/Classes/Demos/PieChartViewController.h new file mode 100644 index 0000000000..3ef54553ba --- /dev/null +++ b/ChartsDemo/Classes/Demos/PieChartViewController.h @@ -0,0 +1,20 @@ +// +// PieChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface PieChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/PieChartViewController.m b/ChartsDemo/Classes/Demos/PieChartViewController.m new file mode 100644 index 0000000000..3182a57478 --- /dev/null +++ b/ChartsDemo/Classes/Demos/PieChartViewController.m @@ -0,0 +1,216 @@ +// +// PieChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "PieChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface PieChartViewController () + +@property (nonatomic, strong) IBOutlet PieChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation PieChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Pie Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Y-Values"}, + @{@"key": @"toggleXValues", @"label": @"Toggle X-Values"}, + @{@"key": @"togglePercent", @"label": @"Toggle Percent"}, + @{@"key": @"toggleHole", @"label": @"Toggle Hole"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"spin", @"label": @"Spin"}, + @{@"key": @"drawCenter", @"label": @"Draw CenterText"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"} + ]; + + _chartView.delegate = self; + + _chartView.usePercentValuesEnabled = YES; + _chartView.holeTransparent = YES; + _chartView.centerTextFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:12.f]; + _chartView.holeRadiusPercent = 0.45f; + _chartView.transparentCircleRadiusPercent = 0.5f; + _chartView.descriptionText = @""; + _chartView.drawCenterTextEnabled = YES; + _chartView.drawHoleEnabled = YES; + _chartView.rotationAngle = 0.f; + _chartView.rotationEnabled = YES; + _chartView.centerText = @"iOS Charts"; + + ChartLegend *l = _chartView.legend; + l.position = ChartLegendPositionRightOfChart; + l.xEntrySpace = 7.f; + l.yEntrySpace = 5.f; + + _sliderX.value = 3.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; + [_chartView animateXYWithDurationX:1.5 durationY:1.5]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + float mult = range; + + NSMutableArray *yVals1 = [[NSMutableArray alloc] init]; + + // IMPORTANT: In a PieChart, no values (Entry) should have the same xIndex (even if from different DataSets), since no values can be drawn above each other. + for (int i = 0; i < count; i++) + { + [yVals1 addObject:[[BarChartDataEntry alloc] initWithValue:(arc4random_uniform(mult) + mult / 5) xIndex:i]]; + } + + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:parties[i % parties.count]]; + } + + PieChartDataSet *dataSet = [[PieChartDataSet alloc] initWithYVals:yVals1 label:@"Election Results"]; + dataSet.sliceSpace = 3.f; + + // add a lot of colors + + NSMutableArray *colors = [[NSMutableArray alloc] init]; + [colors addObjectsFromArray:ChartColorTemplates.vordiplom]; + [colors addObjectsFromArray:ChartColorTemplates.joyful]; + [colors addObjectsFromArray:ChartColorTemplates.colorful]; + [colors addObjectsFromArray:ChartColorTemplates.liberty]; + [colors addObjectsFromArray:ChartColorTemplates.pastel]; + [colors addObject:[UIColor colorWithRed:51/255.f green:181/255.f blue:229/255.f alpha:1.f]]; + + dataSet.colors = colors; + + PieChartData *data = [[PieChartData alloc] initWithXVals:xVals dataSet:dataSet]; + + NSNumberFormatter *pFormatter = [[NSNumberFormatter alloc] init]; + pFormatter.numberStyle = NSNumberFormatterPercentStyle; + pFormatter.maximumFractionDigits = 1; + pFormatter.multiplier = @1.f; + pFormatter.percentSymbol = @" %"; + [data setValueFormatter:pFormatter]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:11.f]]; + [data setValueTextColor:UIColor.whiteColor]; + + _chartView.data = data; + [_chartView highlightValues:nil]; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleXValues"]) + { + _chartView.drawSliceTextEnabled = !_chartView.isDrawSliceTextEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"togglePercent"]) + { + _chartView.usePercentValuesEnabled = !_chartView.isUsePercentValuesEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHole"]) + { + _chartView.drawHoleEnabled = !_chartView.isDrawHoleEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"drawCenter"]) + { + _chartView.drawCenterTextEnabled = !_chartView.isDrawCenterTextEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"spin"]) + { + [_chartView spin:2.0 fromAngle:_chartView.rotationAngle toAngle:_chartView.rotationAngle + 360.f]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/PieChartViewController.xib b/ChartsDemo/Classes/Demos/PieChartViewController.xib new file mode 100644 index 0000000000..dc173618f5 --- /dev/null +++ b/ChartsDemo/Classes/Demos/PieChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/RadarChartViewController.h b/ChartsDemo/Classes/Demos/RadarChartViewController.h new file mode 100644 index 0000000000..e9262c22f6 --- /dev/null +++ b/ChartsDemo/Classes/Demos/RadarChartViewController.h @@ -0,0 +1,20 @@ +// +// RadarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface RadarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/RadarChartViewController.m b/ChartsDemo/Classes/Demos/RadarChartViewController.m new file mode 100644 index 0000000000..3d04e4dc6a --- /dev/null +++ b/ChartsDemo/Classes/Demos/RadarChartViewController.m @@ -0,0 +1,183 @@ +// +// RadarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "RadarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface RadarChartViewController () + +@property (nonatomic, strong) IBOutlet RadarChartView *chartView; + +@end + +@implementation RadarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Radar Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleXLabels", @"label": @"Toggle X-Values"}, + @{@"key": @"toggleYLabels", @"label": @"Toggle Y-Values"}, + @{@"key": @"toggleRotate", @"label": @"Toggle Rotate"}, + @{@"key": @"toggleFill", @"label": @"Toggle Fill"}, + @{@"key": @"spin", @"label": @"Spin"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"} + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.webLineWidth = .75f; + _chartView.innerWebLineWidth = 0.375f; + _chartView.webAlpha = 1.f; + + BalloonMarker *marker = [[BalloonMarker alloc] initWithColor:[UIColor colorWithWhite:180/255. alpha:1.0] font:[UIFont systemFontOfSize:12.0] insets: UIEdgeInsetsMake(8.0, 8.0, 20.0, 8.0)]; + marker.minimumSize = CGSizeMake(80.f, 40.f); + _chartView.marker = marker; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:9.f]; + + ChartYAxis *yAxis = _chartView.yAxis; + yAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:9.f]; + yAxis.labelCount = 5; + yAxis.startAtZeroEnabled = YES; + + ChartLegend *l = _chartView.legend; + l.position = ChartLegendPositionRightOfChart; + l.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + l.xEntrySpace = 7.f; + l.yEntrySpace = 5.f; + + [self setData]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setData +{ + float mult = 150.f; + int count = 9; + + NSMutableArray *yVals1 = [[NSMutableArray alloc] init]; + NSMutableArray *yVals2 = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [yVals1 addObject:[[ChartDataEntry alloc] initWithValue:(arc4random_uniform(mult) + mult / 2) xIndex:i]]; + [yVals2 addObject:[[ChartDataEntry alloc] initWithValue:(arc4random_uniform(mult) + mult / 2) xIndex:i]]; + } + + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:parties[i % parties.count]]; + } + + RadarChartDataSet *set1 = [[RadarChartDataSet alloc] initWithYVals:yVals1 label:@"Set 1"]; + [set1 setColor:ChartColorTemplates.vordiplom[0]]; + set1.drawFilledEnabled = YES; + set1.lineWidth = 2.f; + + RadarChartDataSet *set2 = [[RadarChartDataSet alloc] initWithYVals:yVals2 label:@"Set 2"]; + [set2 setColor:ChartColorTemplates.vordiplom[4]]; + set2.drawFilledEnabled = YES; + set2.lineWidth = 2.f; + + RadarChartData *data = [[RadarChartData alloc] initWithXVals:xVals dataSets:@[set1, set2]]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:8.f]]; + [data setDrawValues:NO]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleXLabels"]) + { + _chartView.xAxis.drawLabelsEnabled = !_chartView.xAxis.isDrawLabelsEnabled; + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleYLabels"]) + { + _chartView.yAxis.drawLabelsEnabled = !_chartView.yAxis.isDrawLabelsEnabled; + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleRotate"]) + { + _chartView.rotationEnabled = !_chartView.isRotationEnabled; + } + + if ([key isEqualToString:@"toggleFill"]) + { + for (RadarChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"spin"]) + { + [_chartView spin:2.0 fromAngle:_chartView.rotationAngle toAngle:_chartView.rotationAngle + 360.f]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/RadarChartViewController.xib b/ChartsDemo/Classes/Demos/RadarChartViewController.xib new file mode 100644 index 0000000000..8bd99e2037 --- /dev/null +++ b/ChartsDemo/Classes/Demos/RadarChartViewController.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/ScatterChartViewController.h b/ChartsDemo/Classes/Demos/ScatterChartViewController.h new file mode 100644 index 0000000000..2b265a76c8 --- /dev/null +++ b/ChartsDemo/Classes/Demos/ScatterChartViewController.h @@ -0,0 +1,20 @@ +// +// ScatterChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface ScatterChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/ScatterChartViewController.m b/ChartsDemo/Classes/Demos/ScatterChartViewController.m new file mode 100644 index 0000000000..f822e85b1f --- /dev/null +++ b/ChartsDemo/Classes/Demos/ScatterChartViewController.m @@ -0,0 +1,249 @@ +// +// ScatterChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "ScatterChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface ScatterChartViewController () + +@property (nonatomic, strong) IBOutlet ScatterChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation ScatterChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Scatter Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.drawGridBackgroundEnabled = NO; + _chartView.highlightEnabled = YES; + _chartView.dragEnabled = YES; + [_chartView setScaleEnabled:YES]; + _chartView.maxVisibleValueCount = 200; + _chartView.pinchZoomEnabled = YES; + + ChartLegend *l = _chartView.legend; + l.position = ChartLegendPositionRightOfChart; + l.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + + ChartYAxis *yl = _chartView.leftAxis; + yl.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + + _chartView.rightAxis.enabled = NO; + + ChartXAxis *xl = _chartView.xAxis; + xl.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + xl.drawGridLinesEnabled = NO; + + _sliderX.value = 45.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i) stringValue]]; + } + + NSMutableArray *yVals1 = [[NSMutableArray alloc] init]; + NSMutableArray *yVals2 = [[NSMutableArray alloc] init]; + NSMutableArray *yVals3 = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float val = (float) (arc4random_uniform(range)) + 3; + [yVals1 addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + + val = (float) (arc4random_uniform(range)) + 3; + [yVals2 addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + + val = (float) (arc4random_uniform(range)) + 3; + [yVals3 addObject:[[ChartDataEntry alloc] initWithValue:val xIndex:i]]; + } + + ScatterChartDataSet *set1 = [[ScatterChartDataSet alloc] initWithYVals:yVals1 label:@"DS 1"]; + set1.scatterShape = ScatterShapeSquare; + [set1 setColor:ChartColorTemplates.colorful[0]]; + ScatterChartDataSet *set2 = [[ScatterChartDataSet alloc] initWithYVals:yVals2 label:@"DS 2"]; + set2.scatterShape = ScatterShapeCircle; + [set2 setColor:ChartColorTemplates.colorful[1]]; + ScatterChartDataSet *set3 = [[ScatterChartDataSet alloc] initWithYVals:yVals3 label:@"DS 3"]; + set3.scatterShape = ScatterShapeCross; + [set3 setColor:ChartColorTemplates.colorful[2]]; + + set1.scatterShapeSize = 8.f; + set2.scatterShapeSize = 8.f; + set3.scatterShapeSize = 8.f; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + [dataSets addObject:set2]; + [dataSets addObject:set3]; + + ScatterChartData *data = [[ScatterChartData alloc] initWithXVals:xVals dataSets:dataSets]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:7.f]]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleFilled"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawFilledEnabled = !set.isDrawFilledEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCircles"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCirclesEnabled = !set.isDrawCirclesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleCubic"]) + { + for (LineChartDataSet *set in _chartView.data.dataSets) + { + set.drawCubicEnabled = !set.isDrawCubicEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/ScatterChartViewController.xib b/ChartsDemo/Classes/Demos/ScatterChartViewController.xib new file mode 100644 index 0000000000..5b8f138e40 --- /dev/null +++ b/ChartsDemo/Classes/Demos/ScatterChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/SinusBarChartViewController.h b/ChartsDemo/Classes/Demos/SinusBarChartViewController.h new file mode 100644 index 0000000000..30edfd29a9 --- /dev/null +++ b/ChartsDemo/Classes/Demos/SinusBarChartViewController.h @@ -0,0 +1,20 @@ +// +// SinusBarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface SinusBarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/SinusBarChartViewController.m b/ChartsDemo/Classes/Demos/SinusBarChartViewController.m new file mode 100644 index 0000000000..45b8557e82 --- /dev/null +++ b/ChartsDemo/Classes/Demos/SinusBarChartViewController.m @@ -0,0 +1,210 @@ +// +// SinusBarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "SinusBarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface SinusBarChartViewController () + +@property (nonatomic, strong) IBOutlet BarChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; + +@end + +@implementation SinusBarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Sinus Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleHighlightArrow", @"label": @"Toggle Highlight Arrow"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.drawBarShadowEnabled = NO; + _chartView.drawValueAboveBarEnabled = YES; + _chartView.maxVisibleValueCount = 60; + _chartView.pinchZoomEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionBottom; + xAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + xAxis.drawGridLinesEnabled = NO; + xAxis.enabled = NO; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + leftAxis.labelCount = 6; + leftAxis.startAtZeroEnabled = NO; + leftAxis.axisMinimum = -2.5f; + leftAxis.axisMaximum = 2.5f; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.drawGridLinesEnabled = NO; + rightAxis.labelFont = [UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]; + rightAxis.labelCount = 6; + rightAxis.startAtZeroEnabled = NO; + rightAxis.axisMinimum = -2.5f; + rightAxis.axisMaximum = 2.5f; + + ChartLegend *l = _chartView.legend; + l.position = ChartLegendPositionBelowChartLeft; + l.form = ChartLegendFormSquare; + l.formSize = 9.f; + l.font = [UIFont systemFontOfSize:11.f]; + l.xEntrySpace = 4.f; + + _sliderX.value = 150.0; + [self slidersValueChanged:nil]; + [_chartView animateXYWithDurationX:2.0 durationY:2.0]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + NSMutableArray *entries = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:[@(i) stringValue]]; + [entries addObject:[[BarChartDataEntry alloc] initWithValue:sinf(M_PI * (i % 128) / 64.f) xIndex:i]]; + } + + BarChartDataSet *set = [[BarChartDataSet alloc] initWithYVals:entries label:@"Sinus Function"]; + set.barSpace = .4f; + [set setColor:[UIColor colorWithRed:240/255.f green:120/255.f blue:124/255.f alpha:1.f]]; + + BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSet:set]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]]; + [data setDrawValues:NO]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlightArrow"]) + { + _chartView.drawHighlightArrowEnabled = !_chartView.isDrawHighlightArrowEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + + [self setDataCount:(_sliderX.value)]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/SinusBarChartViewController.xib b/ChartsDemo/Classes/Demos/SinusBarChartViewController.xib new file mode 100644 index 0000000000..25bed130f6 --- /dev/null +++ b/ChartsDemo/Classes/Demos/SinusBarChartViewController.xib @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Classes/Demos/StackedBarChartViewController.h b/ChartsDemo/Classes/Demos/StackedBarChartViewController.h new file mode 100644 index 0000000000..40bc6fabab --- /dev/null +++ b/ChartsDemo/Classes/Demos/StackedBarChartViewController.h @@ -0,0 +1,20 @@ +// +// StackedBarChartViewController.h +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "DemoBaseViewController.h" +#import + +@interface StackedBarChartViewController : DemoBaseViewController + +@end diff --git a/ChartsDemo/Classes/Demos/StackedBarChartViewController.m b/ChartsDemo/Classes/Demos/StackedBarChartViewController.m new file mode 100644 index 0000000000..5ad4e46f73 --- /dev/null +++ b/ChartsDemo/Classes/Demos/StackedBarChartViewController.m @@ -0,0 +1,224 @@ +// +// StackedBarChartViewController.m +// ChartsDemo +// +// Created by Daniel Cohen Gindi on 17/3/15. +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import "StackedBarChartViewController.h" +#import "ChartsDemo-Swift.h" + +@interface StackedBarChartViewController () + +@property (nonatomic, strong) IBOutlet BarChartView *chartView; +@property (nonatomic, strong) IBOutlet UISlider *sliderX; +@property (nonatomic, strong) IBOutlet UISlider *sliderY; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextX; +@property (nonatomic, strong) IBOutlet UITextField *sliderTextY; + +@end + +@implementation StackedBarChartViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.title = @"Stacked Bar Chart"; + + self.options = @[ + @{@"key": @"toggleValues", @"label": @"Toggle Values"}, + @{@"key": @"toggleHighlight", @"label": @"Toggle Highlight"}, + @{@"key": @"toggleHighlightArrow", @"label": @"Toggle Highlight Arrow"}, + @{@"key": @"animateX", @"label": @"Animate X"}, + @{@"key": @"animateY", @"label": @"Animate Y"}, + @{@"key": @"animateXY", @"label": @"Animate XY"}, + @{@"key": @"toggleStartZero", @"label": @"Toggle StartZero"}, + @{@"key": @"toggleAdjustXLegend", @"label": @"Toggle AdjustXLegend"}, + @{@"key": @"saveToGallery", @"label": @"Save to Camera Roll"}, + @{@"key": @"togglePinchZoom", @"label": @"Toggle PinchZoom"}, + ]; + + _chartView.delegate = self; + + _chartView.descriptionText = @""; + _chartView.noDataTextDescription = @"You need to provide data for the chart."; + + _chartView.maxVisibleValueCount = 60; + _chartView.drawValuesForWholeStackEnabled = YES; + _chartView.pinchZoomEnabled = NO; + _chartView.drawBarShadowEnabled = NO; + _chartView.drawValueAboveBarEnabled = NO; + + ChartYAxis *leftAxis = _chartView.leftAxis; + leftAxis.valueFormatter = [[NSNumberFormatter alloc] init]; + leftAxis.valueFormatter.maximumFractionDigits = 1; + leftAxis.valueFormatter.negativeSuffix = @" $"; + leftAxis.valueFormatter.positiveSuffix = @" $"; + + ChartYAxis *rightAxis = _chartView.rightAxis; + rightAxis.valueFormatter = leftAxis.valueFormatter; + rightAxis.drawGridLinesEnabled = NO; + + ChartXAxis *xAxis = _chartView.xAxis; + xAxis.labelPosition = XAxisLabelPositionTop; + + ChartLegend *l = _chartView.legend; + l.position = ChartLegendPositionBelowChartRight; + l.form = ChartLegendFormSquare; + l.formSize = 8.f; + l.formToTextSpace = 4.f; + l.xEntrySpace = 6.f; + + _sliderX.value = 11.f; + _sliderY.value = 100.f; + [self slidersValueChanged:nil]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)setDataCount:(int)count range:(float)range +{ + NSMutableArray *xVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + [xVals addObject:months[i % 12]]; + } + + NSMutableArray *yVals = [[NSMutableArray alloc] init]; + + for (int i = 0; i < count; i++) + { + float mult = (range + 1); + float val1 = (float) (arc4random_uniform(mult) + mult / 3); + float val2 = (float) (arc4random_uniform(mult) + mult / 3); + float val3 = (float) (arc4random_uniform(mult) + mult / 3); + + [yVals addObject:[[BarChartDataEntry alloc] initWithValues:@[@(val1), @(val2), @(val3)] xIndex:i]]; + } + + BarChartDataSet *set1 = [[BarChartDataSet alloc] initWithYVals:yVals label:@"Statistics Vienna 2014"]; + set1.colors = @[ChartColorTemplates.vordiplom[0], ChartColorTemplates.vordiplom[1], ChartColorTemplates.vordiplom[2]]; + set1.stackLabels = @[@"Births", @"Divorces", @"Marriages"]; + + NSMutableArray *dataSets = [[NSMutableArray alloc] init]; + [dataSets addObject:set1]; + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter = [[NSNumberFormatter alloc] init]; + formatter.maximumFractionDigits = 1; + formatter.negativeSuffix = @" $"; + formatter.positiveSuffix = @" $"; + + BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; + [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:7.f]]; + [data setValueFormatter:formatter]; + + _chartView.data = data; +} + +- (void)optionTapped:(NSString *)key +{ + if ([key isEqualToString:@"toggleValues"]) + { + for (ChartDataSet *set in _chartView.data.dataSets) + { + set.drawValuesEnabled = !set.isDrawValuesEnabled; + } + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlight"]) + { + _chartView.highlightEnabled = !_chartView.isHighlightEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleHighlightArrow"]) + { + _chartView.drawHighlightArrowEnabled = !_chartView.isDrawHighlightArrowEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"toggleStartZero"]) + { + _chartView.leftAxis.startAtZeroEnabled = !_chartView.leftAxis.isStartAtZeroEnabled; + _chartView.rightAxis.startAtZeroEnabled = !_chartView.rightAxis.isStartAtZeroEnabled; + + [_chartView notifyDataSetChanged]; + } + + if ([key isEqualToString:@"animateX"]) + { + [_chartView animateXWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateY"]) + { + [_chartView animateYWithDuration:3.0]; + } + + if ([key isEqualToString:@"animateXY"]) + { + [_chartView animateXYWithDurationX:3.0 durationY:3.0]; + } + + if ([key isEqualToString:@"toggleAdjustXLegend"]) + { + ChartXAxis *xLabels = _chartView.xAxis; + + xLabels.adjustXLabelsEnabled = !xLabels.isAdjustXLabelsEnabled; + + [_chartView setNeedsDisplay]; + } + + if ([key isEqualToString:@"saveToGallery"]) + { + [_chartView saveToCameraRoll]; + } + + if ([key isEqualToString:@"togglePinchZoom"]) + { + _chartView.pinchZoomEnabled = !_chartView.isPinchZoomEnabled; + + [_chartView setNeedsDisplay]; + } +} + +#pragma mark - Actions + +- (IBAction)slidersValueChanged:(id)sender +{ + _sliderTextX.text = [@((int)_sliderX.value + 1) stringValue]; + _sliderTextY.text = [@((int)_sliderY.value) stringValue]; + + [self setDataCount:(_sliderX.value + 1) range:_sliderY.value]; +} + +#pragma mark - ChartViewDelegate + +- (void)chartValueSelected:(__nonnull ChartViewBase *)chartView entry:(__nonnull ChartDataEntry *)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(__nonnull ChartHighlight *)highlight +{ + NSLog(@"chartValueSelected"); +} + +- (void)chartValueNothingSelected:(__nonnull ChartViewBase *)chartView +{ + NSLog(@"chartValueNothingSelected"); +} + +@end diff --git a/ChartsDemo/Classes/Demos/StackedBarChartViewController.xib b/ChartsDemo/Classes/Demos/StackedBarChartViewController.xib new file mode 100644 index 0000000000..a8ce2645f5 --- /dev/null +++ b/ChartsDemo/Classes/Demos/StackedBarChartViewController.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..7997783400 --- /dev/null +++ b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,44 @@ +{ + "images" : [ + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png new file mode 100644 index 0000000000..33b2e63ab4 Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png new file mode 100644 index 0000000000..ef18aba824 Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 0000000000..775e7277c8 Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 0000000000..df4daf6e9d Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 0000000000..df4daf6e9d Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 0000000000..14dd95b4a6 Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..205398ad48 --- /dev/null +++ b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,43 @@ +{ + "images" : [ + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "736h", + "filename" : "Default-736h@3x.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "667h", + "filename" : "Default-667h@2x.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "filename" : "Default@2x.png", + "scale" : "2x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "retina4", + "filename" : "Default-568h@2x.png", + "minimum-system-version" : "7.0", + "orientation" : "portrait", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png new file mode 100644 index 0000000000..25a46804f7 Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png new file mode 100644 index 0000000000..a3bc101148 Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-736h@3x.png new file mode 100644 index 0000000000..cd359b756e Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ diff --git a/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png new file mode 100644 index 0000000000..16802b260b Binary files /dev/null and b/ChartsDemo/Resources/Images.xcassets/LaunchImage.launchimage/Default@2x.png differ diff --git a/ChartsDemo/Resources/app-icon/Icon-29@2x.png b/ChartsDemo/Resources/app-icon/Icon-29@2x.png new file mode 100644 index 0000000000..33b2e63ab4 Binary files /dev/null and b/ChartsDemo/Resources/app-icon/Icon-29@2x.png differ diff --git a/ChartsDemo/Resources/app-icon/Icon-29@3x.png b/ChartsDemo/Resources/app-icon/Icon-29@3x.png new file mode 100644 index 0000000000..ef18aba824 Binary files /dev/null and b/ChartsDemo/Resources/app-icon/Icon-29@3x.png differ diff --git a/ChartsDemo/Resources/app-icon/Icon-40@2x.png b/ChartsDemo/Resources/app-icon/Icon-40@2x.png new file mode 100644 index 0000000000..775e7277c8 Binary files /dev/null and b/ChartsDemo/Resources/app-icon/Icon-40@2x.png differ diff --git a/ChartsDemo/Resources/app-icon/Icon-40@3x.png b/ChartsDemo/Resources/app-icon/Icon-40@3x.png new file mode 100644 index 0000000000..df4daf6e9d Binary files /dev/null and b/ChartsDemo/Resources/app-icon/Icon-40@3x.png differ diff --git a/ChartsDemo/Resources/app-icon/Icon-60@2x.png b/ChartsDemo/Resources/app-icon/Icon-60@2x.png new file mode 100644 index 0000000000..df4daf6e9d Binary files /dev/null and b/ChartsDemo/Resources/app-icon/Icon-60@2x.png differ diff --git a/ChartsDemo/Resources/app-icon/Icon-60@3x.png b/ChartsDemo/Resources/app-icon/Icon-60@3x.png new file mode 100644 index 0000000000..14dd95b4a6 Binary files /dev/null and b/ChartsDemo/Resources/app-icon/Icon-60@3x.png differ diff --git a/ChartsDemo/Resources/app-icon/iTunesArtwork b/ChartsDemo/Resources/app-icon/iTunesArtwork new file mode 100644 index 0000000000..2429a7ef03 Binary files /dev/null and b/ChartsDemo/Resources/app-icon/iTunesArtwork differ diff --git a/ChartsDemo/Resources/app-icon/iTunesArtwork@2x b/ChartsDemo/Resources/app-icon/iTunesArtwork@2x new file mode 100644 index 0000000000..c55f117d42 Binary files /dev/null and b/ChartsDemo/Resources/app-icon/iTunesArtwork@2x differ diff --git a/ChartsDemo/Resources/launch-image/Default-568h@2x.png b/ChartsDemo/Resources/launch-image/Default-568h@2x.png new file mode 100755 index 0000000000..25a46804f7 Binary files /dev/null and b/ChartsDemo/Resources/launch-image/Default-568h@2x.png differ diff --git a/ChartsDemo/Resources/launch-image/Default-667h@2x.png b/ChartsDemo/Resources/launch-image/Default-667h@2x.png new file mode 100755 index 0000000000..a3bc101148 Binary files /dev/null and b/ChartsDemo/Resources/launch-image/Default-667h@2x.png differ diff --git a/ChartsDemo/Resources/launch-image/Default-736h@3x.png b/ChartsDemo/Resources/launch-image/Default-736h@3x.png new file mode 100755 index 0000000000..cd359b756e Binary files /dev/null and b/ChartsDemo/Resources/launch-image/Default-736h@3x.png differ diff --git a/ChartsDemo/Resources/launch-image/Default@2x.png b/ChartsDemo/Resources/launch-image/Default@2x.png new file mode 100755 index 0000000000..16802b260b Binary files /dev/null and b/ChartsDemo/Resources/launch-image/Default@2x.png differ diff --git a/ChartsDemo/Supporting Files/ChartsDemo-Bridging-Header.h b/ChartsDemo/Supporting Files/ChartsDemo-Bridging-Header.h new file mode 100644 index 0000000000..1b2cb5d6d0 --- /dev/null +++ b/ChartsDemo/Supporting Files/ChartsDemo-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/ChartsDemo/Supporting Files/Info.plist b/ChartsDemo/Supporting Files/Info.plist new file mode 100644 index 0000000000..97caf61173 --- /dev/null +++ b/ChartsDemo/Supporting Files/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.dcg.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ChartsDemo/Supporting Files/main.m b/ChartsDemo/Supporting Files/main.m new file mode 100644 index 0000000000..f4d736286e --- /dev/null +++ b/ChartsDemo/Supporting Files/main.m @@ -0,0 +1,22 @@ +// +// main.m +// Charts +// +// Created by Daniel Cohen Gindi on 23/2/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +}