Mercurial > public > lazybear
changeset 101:57e5196feb08
Add LineChartTutorial, BarChartTutorial, implementing LineChart on Stock view
author | Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com> |
---|---|
date | Sun, 31 Jan 2021 18:16:26 +0100 |
parents | f304bb0d8dee |
children | a17ccf20f4a3 |
files | LazyBear.xcodeproj/project.pbxproj LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate lazybear/Jobs/ScalateChart.swift lazybear/LazyBearApp.swift lazybear/Network/IexApi.swift lazybear/Tests/BarChartTutorial.swift lazybear/Tests/LineChartTutorial.swift lazybear/Views/LineChart.swift lazybear/Views/LineChartShape.swift lazybear/Views/Price.swift lazybear/Views/Stock.swift |
diffstat | 11 files changed, 250 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- a/LazyBear.xcodeproj/project.pbxproj Sat Jan 30 19:58:06 2021 +0100 +++ b/LazyBear.xcodeproj/project.pbxproj Sun Jan 31 18:16:26 2021 +0100 @@ -10,6 +10,10 @@ 95002580256D17D9008FFD28 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9500257F256D17D9008FFD28 /* StoreKit.framework */; }; 95078FD125BF4E640004FA75 /* CloudKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95078FD025BF4E640004FA75 /* CloudKitManager.swift */; }; 950B79F625B1CB7A00E5DB5B /* CompanyList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950B79F525B1CB7A00E5DB5B /* CompanyList.swift */; }; + 9520F0AB25C7074D00692610 /* LineChartTutorial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9520F0AA25C7074D00692610 /* LineChartTutorial.swift */; }; + 9520F0AE25C7115100692610 /* BarChartTutorial.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9520F0AD25C7115100692610 /* BarChartTutorial.swift */; }; + 9520F0B225C712D000692610 /* LineChartShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9520F0B125C712D000692610 /* LineChartShape.swift */; }; + 9520F0B525C7131300692610 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9520F0B425C7131300692610 /* LineChart.swift */; }; 9537923625BDF85D0001F82B /* LogoApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9537923525BDF85D0001F82B /* LogoApi.swift */; }; 954D992525A2123B001F7F60 /* HistoricalPricesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954D992425A2123B001F7F60 /* HistoricalPricesModel.swift */; }; 954DDF0425C456E800848A4B /* QuoteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954DDF0325C456E800848A4B /* QuoteModel.swift */; }; @@ -27,7 +31,6 @@ 95B04EB525212369000AD27F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95B04EB425212369000AD27F /* ContentView.swift */; }; 95B04EB72521236A000AD27F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95B04EB62521236A000AD27F /* Assets.xcassets */; }; 95B395A525BDF42E009A7EB0 /* companies.json in Resources */ = {isa = PBXBuildFile; fileRef = 95B395A425BDF42E009A7EB0 /* companies.json */; }; - 95C28AB925BC46250033D16A /* ScalateChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C28AB825BC46250033D16A /* ScalateChart.swift */; }; 95D1BF4925ADCF7700E5D063 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D1BF4825ADCF7700E5D063 /* Persistence.swift */; }; 95E4118F25BEC35D00A9C23F /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 95E4118E25BEC35D00A9C23F /* SDWebImageSwiftUI */; }; 95E4119225BEC56F00A9C23F /* SuperTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E4119125BEC56F00A9C23F /* SuperTitle.swift */; }; @@ -51,6 +54,10 @@ 9500257F256D17D9008FFD28 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 95078FD025BF4E640004FA75 /* CloudKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CloudKitManager.swift; path = LazyBear/CloudKitManager.swift; sourceTree = SOURCE_ROOT; }; 950B79F525B1CB7A00E5DB5B /* CompanyList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompanyList.swift; sourceTree = "<group>"; }; + 9520F0AA25C7074D00692610 /* LineChartTutorial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LineChartTutorial.swift; path = lazybear/Tests/LineChartTutorial.swift; sourceTree = SOURCE_ROOT; }; + 9520F0AD25C7115100692610 /* BarChartTutorial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BarChartTutorial.swift; path = lazybear/Tests/BarChartTutorial.swift; sourceTree = SOURCE_ROOT; }; + 9520F0B125C712D000692610 /* LineChartShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LineChartShape.swift; path = lazybear/Views/LineChartShape.swift; sourceTree = SOURCE_ROOT; }; + 9520F0B425C7131300692610 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LineChart.swift; path = lazybear/Views/LineChart.swift; sourceTree = SOURCE_ROOT; }; 9537923525BDF85D0001F82B /* LogoApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LogoApi.swift; path = LazyBear/Network/LogoApi.swift; sourceTree = SOURCE_ROOT; }; 954D992425A2123B001F7F60 /* HistoricalPricesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HistoricalPricesModel.swift; path = lazybear/Models/HistoricalPricesModel.swift; sourceTree = SOURCE_ROOT; }; 954DDF0325C456E800848A4B /* QuoteModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = QuoteModel.swift; path = lazybear/Models/QuoteModel.swift; sourceTree = SOURCE_ROOT; }; @@ -71,7 +78,6 @@ 95B04EB62521236A000AD27F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 95B04EBB2521236A000AD27F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 95B395A425BDF42E009A7EB0 /* companies.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = companies.json; path = lazybear/Data/companies.json; sourceTree = SOURCE_ROOT; }; - 95C28AB825BC46250033D16A /* ScalateChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScalateChart.swift; path = LazyBear/Jobs/ScalateChart.swift; sourceTree = SOURCE_ROOT; }; 95D1BF4825ADCF7700E5D063 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Persistence.swift; path = LazyBear/Persistence.swift; sourceTree = SOURCE_ROOT; }; 95E4119125BEC56F00A9C23F /* SuperTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuperTitle.swift; sourceTree = "<group>"; }; 95E411A625BEE03000A9C23F /* Watchlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Watchlist.swift; sourceTree = "<group>"; }; @@ -115,6 +121,8 @@ 952F791C2598B1CD00FF929F /* Tests */ = { isa = PBXGroup; children = ( + 9520F0AA25C7074D00692610 /* LineChartTutorial.swift */, + 9520F0AD25C7115100692610 /* BarChartTutorial.swift */, ); path = Tests; sourceTree = "<group>"; @@ -149,6 +157,8 @@ 95AD892325C5D8A200BCE8E4 /* AddWatchlist.swift */, 95F6C30425BAF599003CF389 /* CompanyHeader.swift */, 95F6C30825BAF7C2003CF389 /* DateSelection.swift */, + 9520F0B125C712D000692610 /* LineChartShape.swift */, + 9520F0B425C7131300692610 /* LineChart.swift */, ); path = Views; sourceTree = "<group>"; @@ -157,7 +167,6 @@ isa = PBXGroup; children = ( 95AB4A79259DCBAE0064C9C1 /* ReadJson.swift */, - 95C28AB825BC46250033D16A /* ScalateChart.swift */, ); path = Jobs; sourceTree = "<group>"; @@ -300,10 +309,13 @@ 95F6F45C25C20D8D002AC66A /* Price.swift in Sources */, 9597CE0125C1DC0A004DDFED /* LogoModifier.swift in Sources */, 954DDF0425C456E800848A4B /* QuoteModel.swift in Sources */, + 9520F0B525C7131300692610 /* LineChart.swift in Sources */, 9597CE0425C1DFE7004DDFED /* LogoPlaceholder.swift in Sources */, 95FE646B25C30B880052832E /* ApiModel.swift in Sources */, 95F6C30525BAF599003CF389 /* CompanyHeader.swift in Sources */, 95612C512598D48200F7698F /* SearchBar.swift in Sources */, + 9520F0AE25C7115100692610 /* BarChartTutorial.swift in Sources */, + 9520F0AB25C7074D00692610 /* LineChartTutorial.swift in Sources */, 95E411BE25BEEA6C00A9C23F /* WatchlistRow.swift in Sources */, 95AD892425C5D8A200BCE8E4 /* AddWatchlist.swift in Sources */, 950B79F625B1CB7A00E5DB5B /* CompanyList.swift in Sources */, @@ -321,8 +333,8 @@ 95AB4A7D259DCC0C0064C9C1 /* CompanyModel.swift in Sources */, 95700BC625BD9D12009CEEFE /* IexApi.swift in Sources */, 9537923625BDF85D0001F82B /* LogoApi.swift in Sources */, - 95C28AB925BC46250033D16A /* ScalateChart.swift in Sources */, 95E411B625BEE84E00A9C23F /* Stock.swift in Sources */, + 9520F0B225C712D000692610 /* LineChartShape.swift in Sources */, 954D992525A2123B001F7F60 /* HistoricalPricesModel.swift in Sources */, 95FE646725C2DC580052832E /* WatchlistCompany+CoreDataClass.swift in Sources */, 95E411A725BEE03000A9C23F /* Watchlist.swift in Sources */,
Binary file LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate has changed
--- a/lazybear/Jobs/ScalateChart.swift Sat Jan 30 19:58:06 2021 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -// -// ScalateChart.swift -// LazyBear -// -// Created by Dennis Concepción Martín on 23/1/21. -// - -import SwiftUI - -func scalateChart(prices: [Double], selectedPeriod: Int) -> [Double] { - // Remove every two items to shorter the chart data points - var indexesToRemove = Set<Int>() - - if selectedPeriod >= 4 { - for index in 0..<prices.count { - indexesToRemove.insert(index*2) - } - } - let prices = prices - .enumerated() - .filter { !indexesToRemove.contains($0.offset) } - .map { $0.element } - - return prices -}
--- a/lazybear/LazyBearApp.swift Sat Jan 30 19:58:06 2021 +0100 +++ b/lazybear/LazyBearApp.swift Sun Jan 31 18:16:26 2021 +0100 @@ -19,4 +19,14 @@ .environmentObject(apiAccess) // Api info (url and token) } } + + // Line chart tutorial + func randomSample() -> [Double] { + var randomArray = [Double]() + for _ in 0..<100 { + randomArray.append(Double.random(in: 1...100)) + } + + return randomArray + } }
--- a/lazybear/Network/IexApi.swift Sat Jan 30 19:58:06 2021 +0100 +++ b/lazybear/Network/IexApi.swift Sun Jan 31 18:16:26 2021 +0100 @@ -59,7 +59,7 @@ var path: String { switch self { case .chartCloseOnly: - return "chartCloseOnly=true" + return "chartCloseOnly=true&" } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lazybear/Tests/BarChartTutorial.swift Sun Jan 31 18:16:26 2021 +0100 @@ -0,0 +1,41 @@ +// +// BarChartTutorial.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 31/1/21. +// + +import SwiftUI + +struct DataPoint2: Identifiable { + let id: Int + let values: Double + let color: Color + let title: String + + init(value: Double, color: Color, title: String = "") { + self.id = Int.random(in: 1..<Int.max) + self.values = value + self.color = color + self.title = title + } + + init(id: Int, value: Double, color: Color, title: String = "") { + self.id = Int.random(in: 1..<Int.max) + self.values = value + self.color = color + self.title = title + } +} + +struct BarChartTutorial: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +struct BarChartTutorial_Previews: PreviewProvider { + static var previews: some View { + BarChartTutorial() + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lazybear/Tests/LineChartTutorial.swift Sun Jan 31 18:16:26 2021 +0100 @@ -0,0 +1,52 @@ +// +// LineChartTutorial.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 31/1/21. +// + +import SwiftUI + +struct LineChartTutorial: View { + @State private var data = makeDataPoints() + + var body: some View { + LineChart(dataPoints: data, lineColor: .blue, lineWidth: 5, pointColor: .red, pointSize: 10) + .frame(width: 300, height: 200) + .onTapGesture { + data = Self.makeDataPoints() + } + } + + // Generate random data usefull for representation + static func makeDataPoints() -> [DataPoint] { + var isGoingUp = true + var currentValue = 50.0 + + return (1...50).map { _ in + if isGoingUp { + currentValue += Double.random(in: 1...10) + } else { + currentValue -= Double.random(in: 1...10) + } + + if isGoingUp { + if Int.random(in: 0..<10) == 0 { + isGoingUp.toggle() + } + } else { + if Int.random(in: 0..<7) == 0 { + isGoingUp.toggle() + } + } + + return DataPoint(value: abs(currentValue)) + } + } +} + +struct LineChartTutorial_Previews: PreviewProvider { + static var previews: some View { + LineChartTutorial() + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lazybear/Views/LineChart.swift Sun Jan 31 18:16:26 2021 +0100 @@ -0,0 +1,33 @@ +// +// LineChart.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 31/1/21. +// + +import SwiftUI + +struct LineChart: View { + let dataPoints: [DataPoint] + var lineColor = Color.primary + var lineWidth: CGFloat = 2 + + var pointColor = Color.primary + var pointSize: CGFloat = 5 + + var body: some View { + ZStack { + if lineColor != .clear { + LineChartShape(dataPoints: dataPoints, pointSize: pointSize, drawingLines: true) + .stroke(lineColor, lineWidth: lineWidth) + } + + /* + if lineColor != .clear { + LineChartShape(dataPoints: dataPoints, pointSize: pointSize, drawingLines: false) + .fill(pointColor) + } + */ + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lazybear/Views/LineChartShape.swift Sun Jan 31 18:16:26 2021 +0100 @@ -0,0 +1,61 @@ +// +// LineChartShape.swift +// LazyBear +// +// Created by Dennis Concepción Martín on 31/1/21. +// + +import SwiftUI + +struct DataPoint { + let value: Double +} + +struct LineChartShape: Shape { + let dataPoints: [DataPoint] + let pointSize: CGFloat + let maxValue: Double + let drawingLines: Bool + + init(dataPoints: [DataPoint], pointSize: CGFloat, drawingLines: Bool) { + self.dataPoints = dataPoints + self.pointSize = pointSize + self.drawingLines = drawingLines + + let highestPoint = dataPoints.max { $0.value < $1.value } + maxValue = highestPoint?.value ?? 1 + } + + func path(in rect: CGRect) -> Path { + var path = Path() + let drawRect = rect.insetBy(dx: pointSize, dy: pointSize) + + let xMultiplier = drawRect.width / CGFloat(dataPoints.count - 1) + let yMultiplier = drawRect.height / CGFloat(maxValue) + + for(index, dataPoint) in dataPoints.enumerated() { + var x = xMultiplier * CGFloat(index) + var y = yMultiplier * CGFloat(dataPoint.value) + + y = drawRect.height - y + + x += drawRect.minX + y += drawRect.minY + + if drawingLines { + if index == 0 { + path.move(to: CGPoint(x: x, y: y)) + } else { + path.addLine(to: CGPoint(x: x, y: y)) + } + } else { + x -= pointSize / 2 + y -= pointSize / 2 + + path.addEllipse(in: CGRect(x: x, y: y, width: pointSize, height: pointSize)) + } + } + + return path + } +}
--- a/lazybear/Views/Price.swift Sat Jan 30 19:58:06 2021 +0100 +++ b/lazybear/Views/Price.swift Sun Jan 31 18:16:26 2021 +0100 @@ -12,12 +12,12 @@ @State var symbol: String @State var showVertical: Bool - @State var url = String() { didSet { giveMePrices() }} - @State var showingView = false + @State private var url = String() { didSet { requestPrice() }} + @State private var showingView = false @State var latestPrice = Float() @State var changePercent = Double() { didSet { self.showingView = true }} - @State var negativeChange = false + @State private var negativeChange = false let iexApi = IexApi() // Request api function let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() // Set recurrent price request @@ -46,14 +46,16 @@ .onDisappear { self.timer.upstream.connect().cancel() } // Stop timer } - private func getUrl() { + private func getUrl() { let baseUrl = apiAccess.results[1].url ?? "" // 1 -> Sandbox / 2 -> Production let token = apiAccess.results[1].key ?? "" let path = iexApi.getPath(version: .stable, stock: .symbol(company: symbol), endpoint: .quote, range: nil, parameters: nil) + self.url = baseUrl + path + token + } - private func giveMePrices() { + private func requestPrice() { iexApi.request(url: url, model: QuoteModel.self) { result in self.latestPrice = result.latestPrice if self.changePercent >= 0 { self.negativeChange = true }
--- a/lazybear/Views/Stock.swift Sat Jan 30 19:58:06 2021 +0100 +++ b/lazybear/Views/Stock.swift Sun Jan 31 18:16:26 2021 +0100 @@ -11,8 +11,17 @@ var name: String var symbol: String + var period = ["1W", "1M", "3M", "6M", "1Y", "2Y", "5Y"] @State var selectedPeriod = 2 + @State private var url = String() { didSet { requestHistorical() }} + @State private var data = [HistoricalPricesModel]() { didSet { self.showingLineChart = true }} + @State private var showingLineChart = false + + let iexApi = IexApi() // Request api function + + @EnvironmentObject var apiAccess: ApiAccess + @Environment(\.managedObjectContext) private var viewContext @FetchRequest(entity: WatchlistCompany.entity(), sortDescriptors: []) var companies: FetchedResults<WatchlistCompany> // Fetch core data @@ -32,8 +41,28 @@ Divider() DateSelection(selectedperiod: $selectedPeriod) + .onChange(of: selectedPeriod, perform: { (value) in + getUrl(range: period[selectedPeriod]) + }) + + let prices = data.map { DataPoint(value: $0.close) } + LineChart(dataPoints: prices, lineColor: .green, lineWidth: 2) + .frame(height: 400) } .padding([.leading, .trailing]) + .onAppear { getUrl(range: period[selectedPeriod]) } + } + + private func getUrl(range: String) { + let baseUrl = apiAccess.results[1].url ?? "" // 1 -> Sandbox / 2 -> Production + let token = apiAccess.results[1].key ?? "" + let path = iexApi.getPath(version: .stable, stock: .symbol(company: symbol), endpoint: .historicalPrices, range: .period(range: range), parameters: .chartCloseOnly) + + self.url = baseUrl + path + token + } + + private func requestHistorical() { + iexApi.request(url: url, model: [HistoricalPricesModel].self) { self.data = $0 } } }