diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..45b606ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Cookie cutter +# to ignore any cookie cutter generated files and directories + +cookiecutter.json +{{ cookiecutter.* + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +# Extra +visualization/app/VisualReport/VisualReport.xcodeproj/project.xcworkspace/xcuserdata/nimble.xcuserdatad/UserInterfaceState.xcuserstate + +# Extra2 +visualization/app/VisualReport/VisualReport.xcodeproj/xcuserdata/nimble.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist + diff --git a/visualization/app/VisualReport/VisualReport.xcodeproj/project.pbxproj b/visualization/app/VisualReport/VisualReport.xcodeproj/project.pbxproj index 96e71ec5..02a1d8be 100644 --- a/visualization/app/VisualReport/VisualReport.xcodeproj/project.pbxproj +++ b/visualization/app/VisualReport/VisualReport.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 043E74DE2BABF81700B9AF91 /* BarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043E74DD2BABF81700B9AF91 /* BarChartView.swift */; }; 043E74E02BABF81F00B9AF91 /* BarChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043E74DF2BABF81F00B9AF91 /* BarChartViewModel.swift */; }; 043E74E32BAC210E00B9AF91 /* SwiftCSV in Frameworks */ = {isa = PBXBuildFile; productRef = 043E74E22BAC210E00B9AF91 /* SwiftCSV */; }; + 043EE2FE2C6F5E0B00F60278 /* LineChartScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043EE2FD2C6F5E0B00F60278 /* LineChartScreen.swift */; }; 048B86C12C67D6CB008036D4 /* ProductSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B86C02C67D6CB008036D4 /* ProductSelectionScreen.swift */; }; 048B86C32C67D6D8008036D4 /* ProductSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B86C22C67D6D8008036D4 /* ProductSelectionViewModel.swift */; }; 048B86C62C67E6D0008036D4 /* CSVDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B86C52C67E6D0008036D4 /* CSVDataModel.swift */; }; @@ -21,6 +22,9 @@ 048B86CC2C67E9CB008036D4 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B86CB2C67E9CB008036D4 /* DateFormatter+Extension.swift */; }; 048B86D02C6B58D6008036D4 /* SingleBarScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B86CF2C6B58D6008036D4 /* SingleBarScreen.swift */; }; 048B86D22C6B58E9008036D4 /* SingleBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048B86D12C6B58E9008036D4 /* SingleBarViewModel.swift */; }; + 04DB73752C725FDF0038AEFD /* LineChartDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DB73742C725FDF0038AEFD /* LineChartDataModel.swift */; }; + 04DB73772C7265300038AEFD /* LineChartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DB73762C7265300038AEFD /* LineChartViewModel.swift */; }; + 04DB73792C7346140038AEFD /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DB73782C7346140038AEFD /* Double+Extension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,6 +36,7 @@ 043E74D62BABF77800B9AF91 /* VisualReport.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VisualReport.entitlements; sourceTree = ""; }; 043E74DD2BABF81700B9AF91 /* BarChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartView.swift; sourceTree = ""; }; 043E74DF2BABF81F00B9AF91 /* BarChartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartViewModel.swift; sourceTree = ""; }; + 043EE2FD2C6F5E0B00F60278 /* LineChartScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartScreen.swift; sourceTree = ""; }; 048B86C02C67D6CB008036D4 /* ProductSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductSelectionScreen.swift; sourceTree = ""; }; 048B86C22C67D6D8008036D4 /* ProductSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductSelectionViewModel.swift; sourceTree = ""; }; 048B86C52C67E6D0008036D4 /* CSVDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSVDataModel.swift; sourceTree = ""; }; @@ -39,6 +44,9 @@ 048B86CB2C67E9CB008036D4 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; 048B86CF2C6B58D6008036D4 /* SingleBarScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleBarScreen.swift; sourceTree = ""; }; 048B86D12C6B58E9008036D4 /* SingleBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleBarViewModel.swift; sourceTree = ""; }; + 04DB73742C725FDF0038AEFD /* LineChartDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartDataModel.swift; sourceTree = ""; }; + 04DB73762C7265300038AEFD /* LineChartViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartViewModel.swift; sourceTree = ""; }; + 04DB73782C7346140038AEFD /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -104,6 +112,15 @@ path = BarChart; sourceTree = ""; }; + 043EE2FC2C6F5DF600F60278 /* LineChart */ = { + isa = PBXGroup; + children = ( + 043EE2FD2C6F5E0B00F60278 /* LineChartScreen.swift */, + 04DB73762C7265300038AEFD /* LineChartViewModel.swift */, + ); + path = LineChart; + sourceTree = ""; + }; 048B86BF2C67D630008036D4 /* ProductSelection */ = { isa = PBXGroup; children = ( @@ -117,6 +134,7 @@ isa = PBXGroup; children = ( 048B86C52C67E6D0008036D4 /* CSVDataModel.swift */, + 04DB73742C725FDF0038AEFD /* LineChartDataModel.swift */, ); path = DataModel; sourceTree = ""; @@ -133,6 +151,7 @@ isa = PBXGroup; children = ( 048B86CB2C67E9CB008036D4 /* DateFormatter+Extension.swift */, + 04DB73782C7346140038AEFD /* Double+Extension.swift */, ); path = Extension; sourceTree = ""; @@ -140,6 +159,7 @@ 048B86CD2C6B58B7008036D4 /* Graph */ = { isa = PBXGroup; children = ( + 043EE2FC2C6F5DF600F60278 /* LineChart */, 048B86CE2C6B58BE008036D4 /* SingleBar */, ); path = Graph; @@ -233,14 +253,18 @@ 048B86C32C67D6D8008036D4 /* ProductSelectionViewModel.swift in Sources */, 043E74D02BABF77700B9AF91 /* ContentView.swift in Sources */, 048B86C12C67D6CB008036D4 /* ProductSelectionScreen.swift in Sources */, + 04DB73752C725FDF0038AEFD /* LineChartDataModel.swift in Sources */, 048B86D22C6B58E9008036D4 /* SingleBarViewModel.swift in Sources */, 048B86D02C6B58D6008036D4 /* SingleBarScreen.swift in Sources */, 048B86C62C67E6D0008036D4 /* CSVDataModel.swift in Sources */, + 043EE2FE2C6F5E0B00F60278 /* LineChartScreen.swift in Sources */, + 04DB73792C7346140038AEFD /* Double+Extension.swift in Sources */, 043E74E02BABF81F00B9AF91 /* BarChartViewModel.swift in Sources */, 048B86CC2C67E9CB008036D4 /* DateFormatter+Extension.swift in Sources */, 048B86C92C67E75C008036D4 /* CSVFileReader.swift in Sources */, 043E74CE2BABF77700B9AF91 /* VisualReportApp.swift in Sources */, 043E74DE2BABF81700B9AF91 /* BarChartView.swift in Sources */, + 04DB73772C7265300038AEFD /* LineChartViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/visualization/app/VisualReport/VisualReport.xcodeproj/project.xcworkspace/xcuserdata/nimble.xcuserdatad/UserInterfaceState.xcuserstate b/visualization/app/VisualReport/VisualReport.xcodeproj/project.xcworkspace/xcuserdata/nimble.xcuserdatad/UserInterfaceState.xcuserstate index 289e1f1b..0d987876 100644 Binary files a/visualization/app/VisualReport/VisualReport.xcodeproj/project.xcworkspace/xcuserdata/nimble.xcuserdatad/UserInterfaceState.xcuserstate and b/visualization/app/VisualReport/VisualReport.xcodeproj/project.xcworkspace/xcuserdata/nimble.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/visualization/app/VisualReport/VisualReport.xcodeproj/xcuserdata/nimble.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/visualization/app/VisualReport/VisualReport.xcodeproj/xcuserdata/nimble.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist deleted file mode 100644 index e6f330de..00000000 --- a/visualization/app/VisualReport/VisualReport.xcodeproj/xcuserdata/nimble.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/visualization/app/VisualReport/VisualReport/BarChart/BarChartView.swift b/visualization/app/VisualReport/VisualReport/BarChart/BarChartView.swift index 4c375154..2f0b7422 100644 --- a/visualization/app/VisualReport/VisualReport/BarChart/BarChartView.swift +++ b/visualization/app/VisualReport/VisualReport/BarChart/BarChartView.swift @@ -140,23 +140,3 @@ struct BarChartData: Identifiable { self.calculatedValue = calculatedValue } } - -extension BarChartData { - - static var dummyList: [BarChartData] { - [ - .init(name: "January", value: 160), - .init(name: "February", value: 140), - .init(name: "March", value: 130), - .init(name: "April", value: 180), - .init(name: "May", value: 160), - .init(name: "June", value: 190), - .init(name: "July", value: 260), - .init(name: "August", value: 460), - .init(name: "September", value: 177), - .init(name: "October", value: 199), - .init(name: "November", value: 160), - .init(name: "December", value: 80) - ] - } -} diff --git a/visualization/app/VisualReport/VisualReport/BarChart/BarChartViewModel.swift b/visualization/app/VisualReport/VisualReport/BarChart/BarChartViewModel.swift index c53f3073..16801384 100644 --- a/visualization/app/VisualReport/VisualReport/BarChart/BarChartViewModel.swift +++ b/visualization/app/VisualReport/VisualReport/BarChart/BarChartViewModel.swift @@ -65,7 +65,13 @@ final class BarChartViewModel: ObservableObject { var avg = 0.0 barChartUIModel = barChartData.map { barChartData in let paddedName = String(repeating: " ", count: maxValueLength - barChartData.name.count + 1) + barChartData.name - let mappedValue = (barChartData.value - minValue) / (maxValue - minValue) * 90 + 10 + var mappedValue = (barChartData.value - minValue) / (maxValue - minValue) * 90 + 10 + if mappedValue < 0 { + mappedValue = 0 + } + if maxValue == minValue { + mappedValue = 100.0 + } avg += barChartData.value return BarChartData( name: paddedName, diff --git a/visualization/app/VisualReport/VisualReport/ContentView.swift b/visualization/app/VisualReport/VisualReport/ContentView.swift index 65669637..0355dfec 100644 --- a/visualization/app/VisualReport/VisualReport/ContentView.swift +++ b/visualization/app/VisualReport/VisualReport/ContentView.swift @@ -60,7 +60,10 @@ struct ContentView: View { nameView } .navigationDestination(for: CSVDataModel.self) { data in - ProductSelectionScreen(viewModel: ProductSelectionViewModel(csvData: data)) + ProductSelectionScreen(viewModel: ProductSelectionViewModel(csvData: data), navigationPath: $navigationPath) + } + .navigationDestination(for: SingleBarViewModelData.self) { data in + SingleBarScreen(viewModel: SingleBarViewModel(singleBarViewModelData: data)) } } } diff --git a/visualization/app/VisualReport/VisualReport/DataModel/LineChartDataModel.swift b/visualization/app/VisualReport/VisualReport/DataModel/LineChartDataModel.swift new file mode 100644 index 00000000..e3b55e16 --- /dev/null +++ b/visualization/app/VisualReport/VisualReport/DataModel/LineChartDataModel.swift @@ -0,0 +1,34 @@ +// +// LineChartDataModel.swift +// VisualReport +// +// Created by Taher's nimble macbook on 18/8/24. +// + +import Foundation + +struct LineChartDataSeries: Identifiable { + + let id = UUID() + let type: String + var firstDate: Date + var lastDate: Date + var lineChartDataList: [LineChartDataModel] + var ruleMarkDataList: [RuleMarkDataModel] +} + + +struct LineChartDataModel: Identifiable { + + var id: Int + let xTimeValue: Date + let yValue: Double +} + +struct RuleMarkDataModel: Identifiable { + + let id: Int + let yValue: Double + let yName: String + let ruleMarkName: String +} diff --git a/visualization/app/VisualReport/VisualReport/Extension/DateFormatter+Extension.swift b/visualization/app/VisualReport/VisualReport/Extension/DateFormatter+Extension.swift index 879beea9..10be5fc9 100644 --- a/visualization/app/VisualReport/VisualReport/Extension/DateFormatter+Extension.swift +++ b/visualization/app/VisualReport/VisualReport/Extension/DateFormatter+Extension.swift @@ -29,4 +29,11 @@ extension DateFormatter { dateFormatter.dateFormat = "yyyy-MM" return dateFormatter }() + + public static let monthYearShort: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.calendar = Calendar(identifier: .gregorian) + dateFormatter.dateFormat = "yy-MM" + return dateFormatter + }() } diff --git a/visualization/app/VisualReport/VisualReport/Extension/Double+Extension.swift b/visualization/app/VisualReport/VisualReport/Extension/Double+Extension.swift new file mode 100644 index 00000000..85c73fc8 --- /dev/null +++ b/visualization/app/VisualReport/VisualReport/Extension/Double+Extension.swift @@ -0,0 +1,17 @@ +// +// Double+Extension.swift +// VisualReport +// +// Created by Taher's nimble macbook on 19/8/24. +// + +extension Double { + + var fractionTwoDigitString:String { + return String(format: "%.2f", self) + } + + var fractionOneDigitString:String { + return String(format: "%.1f", self) + } +} diff --git a/visualization/app/VisualReport/VisualReport/Graph/LineChart/LineChartScreen.swift b/visualization/app/VisualReport/VisualReport/Graph/LineChart/LineChartScreen.swift new file mode 100644 index 00000000..b532f98f --- /dev/null +++ b/visualization/app/VisualReport/VisualReport/Graph/LineChart/LineChartScreen.swift @@ -0,0 +1,92 @@ +// +// LineChartScreen.swift +// VisualReport +// +// Created by Taher's nimble macbook on 16/8/24. +// + +import Charts +import SwiftUI + +struct LineChartScreen: View { + @StateObject private var viewModel: LineChartViewModel + + @ViewBuilder private var chartView: some View { + if let firstDate = viewModel.uiFirstDate, + let lastDate = viewModel.uiLastDate { + Chart($viewModel.uiModels) { $dataSeries in + ForEach($dataSeries.lineChartDataList) { $data in + LineMark(x: .value("Date", data.xTimeValue, unit: .day), + y: .value("Price", data.yValue)) + } + .foregroundStyle(by: .value("Chart type", dataSeries.type)) + ForEach($dataSeries.ruleMarkDataList) { $data in + RuleMark(y: .value(data.yName, data.yValue)) + .foregroundStyle(Color.orange) + .lineStyle(StrokeStyle(lineWidth: 1)) + .annotation(position: .bottom, + alignment: .bottomLeading) { + Text("\(data.ruleMarkName): \(data.yValue.fractionTwoDigitString)") + .foregroundColor(.orange) + } + } + } + .chartYAxis{ + AxisMarks(position: .leading, values: viewModel.chartYAxisValues) + } + .chartXAxis{ + AxisMarks(position: .bottom, values: viewModel.chartXAxisValues) { value in + AxisGridLine() + AxisTick() + AxisValueLabel() { + if let date = value.as(Date.self) { + Text(DateFormatter.monthYearShort.string(from: date)) + } + } + } + } + .chartXScale( + domain: firstDate...lastDate + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + + private var topSection: some View { + VStack(alignment: .center, spacing: .zero) { + Text(viewModel.viewModelData.chartName) + .foregroundStyle(Color.purple) + .font(.system(size: 32.0, weight: .bold)) + .padding(.bottom, 8.0) + Text(viewModel.viewModelData.timeFrame) + .foregroundStyle(Color.green) + .font(.system(size: 22.0, weight: .bold)) + .padding(.bottom, 24.0) + HStack { + Text(viewModel.viewModelData.weightUnitText) + Spacer() + Text(viewModel.viewModelData.currencyUnitText) + Spacer() + Text(viewModel.viewModelData.dataSource) + } + .font(.system(size: 14.0, weight: .bold)) + .foregroundStyle(Color.purple) + } + .padding(.bottom, 24.0) + } + + var body: some View { + VStack { + topSection + chartView + } + .padding() + .onAppear { + viewModel.createDataModel() + } + } + + init(viewModel: LineChartViewModel) { + _viewModel = StateObject(wrappedValue: viewModel) + } +} diff --git a/visualization/app/VisualReport/VisualReport/Graph/LineChart/LineChartViewModel.swift b/visualization/app/VisualReport/VisualReport/Graph/LineChart/LineChartViewModel.swift new file mode 100644 index 00000000..6f611e93 --- /dev/null +++ b/visualization/app/VisualReport/VisualReport/Graph/LineChart/LineChartViewModel.swift @@ -0,0 +1,93 @@ +// +// LineChartViewModel.swift +// VisualReport +// +// Created by Taher's nimble macbook on 19/8/24. +// + +import Foundation + +final class LineChartViewModel: ObservableObject { + + @Published var uiModels: [LineChartDataSeries] + @Published var uiFirstDate: Date? + @Published var uiLastDate: Date? + @Published var chartYAxisValues: [Int] = [] + @Published var chartXAxisValues: [Date] = [] + let viewModelData: LineChartViewModelData + + init(lineChartViewModelData: LineChartViewModelData) { + uiModels = [] + viewModelData = lineChartViewModelData + } + + func createDataModel() { + guard uiModels.isEmpty else { + return + } + guard let firstDate = viewModelData.lineChartDataList.first?.xTimeValue, + let lastDate = viewModelData.lineChartDataList.last?.xTimeValue else { + return + } + uiFirstDate = firstDate + uiLastDate = lastDate + let ruleMarks = getRuleMarkList( + chartDataPoints: viewModelData.lineChartDataList + ) + let uiModel = LineChartDataSeries( + type: "All year", + firstDate: firstDate, + lastDate: lastDate, + lineChartDataList: viewModelData.lineChartDataList, + ruleMarkDataList: ruleMarks + ) + uiModels = [uiModel] + + // Chart Data + let maxValue = Int(viewModelData.lineChartDataList.map { $0.yValue }.max() ?? 0.0) + 20 + var minValue = Int(viewModelData.lineChartDataList.map { $0.yValue }.min() ?? 0.0) - 20 + if minValue < 0 { + minValue = 0 + } + chartYAxisValues = stride(from: minValue, to: maxValue, by: 20).map { $0 } + + chartXAxisValues.removeAll() + var currentDate = firstDate + let calendar = Calendar.current + let dayInterval = Int(viewModelData.lineChartDataList.count / 10) + while currentDate <= lastDate { + chartXAxisValues.append(currentDate) + if let nextDate = calendar.date(byAdding: .day, value: dayInterval, to: currentDate) { + currentDate = nextDate + } else { + break + } + } + } + + private func getRuleMarkList( + chartDataPoints: [LineChartDataModel] + ) -> [RuleMarkDataModel] { + let avgValue: Double = chartDataPoints.reduce(0.0) { + return $1.yValue + $0 + } / Double(chartDataPoints.count) + return [ + RuleMarkDataModel( + id: 1, + yValue: avgValue, + yName: "Average price:", + ruleMarkName: "Average price" + ) + ] + } +} + +struct LineChartViewModelData { + + let chartName: String + let timeFrame: String + let weightUnitText: String + let currencyUnitText: String + let dataSource: String + let lineChartDataList: [LineChartDataModel] +} diff --git a/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarScreen.swift b/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarScreen.swift index 93cea73d..7ed8c980 100644 --- a/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarScreen.swift +++ b/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarScreen.swift @@ -11,22 +11,20 @@ struct SingleBarScreen: View { @StateObject private var viewModel: SingleBarViewModel + private var barChartView: some View { - let barChartViewModel = BarChartViewModel( - chartName: "Price chart for \(viewModel.productName)", - timeFrame: viewModel.timeframe, - weightUnitText: "Weight unit: \(viewModel.quantity) \(viewModel.quantityUnit)", - currencyUnitText: "Currency unit: BDT(৳)", - dataSource: "Data collected from ChalDal.com", - xAxisName: "Timestamp", - yAxisName: "Price", - barChartData: viewModel.barChartData - ) - return BarChartView(viewModel: barChartViewModel) + return BarChartView(viewModel: viewModel.barChartViewModel) } var body: some View { - barChartView + TabView { + barChartView + .navigationTitle("Single Bar") + .tabItem { Text("Bar") } + LineChartScreen(viewModel: viewModel.lineChartViewModel) + .navigationTitle("Line chart") + .tabItem { Text("Line Chart") } + } } init(viewModel: SingleBarViewModel) { diff --git a/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarViewModel.swift b/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarViewModel.swift index 963662e6..aa649049 100644 --- a/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarViewModel.swift +++ b/visualization/app/VisualReport/VisualReport/Graph/SingleBar/SingleBarViewModel.swift @@ -7,37 +7,75 @@ import Foundation -final class SingleBarViewModel: ObservableObject { +struct SingleBarViewModelData: Hashable { - + let csvData: CSVDataModel let productName: String let quantity: Double +} + +final class SingleBarViewModel: ObservableObject { @Published private(set) var barChartData: [BarChartData] = [] @Published private(set) var timeframe: String = "" @Published private(set) var quantityUnit: String = "" - private let csvData: CSVDataModel - init(csvData: CSVDataModel, productName: String, quantity: Double) { - print("pr name: \(productName) qt: \(quantity)") - self.csvData = csvData - self.productName = productName - self.quantity = quantity + let singleBarViewModelData: SingleBarViewModelData + private var lineChartDataModelList: [LineChartDataModel] = [] + + init(singleBarViewModelData: SingleBarViewModelData) { + self.singleBarViewModelData = singleBarViewModelData populateData() } + var barChartViewModel: BarChartViewModel { + BarChartViewModel( + chartName: "Price chart for \(singleBarViewModelData.productName)", + timeFrame: timeframe, + weightUnitText: "Weight unit: \(singleBarViewModelData.quantity) \(quantityUnit)", + currencyUnitText: "Currency unit: BDT(৳)", + dataSource: "Data collected from ChalDal.com", + xAxisName: "Timestamp", + yAxisName: "Price", + barChartData: barChartData + ) + } + + var lineChartViewModel: LineChartViewModel { + LineChartViewModel( + lineChartViewModelData: LineChartViewModelData( + chartName: "Price chart for \(singleBarViewModelData.productName)", + timeFrame: timeframe, + weightUnitText: "Weight unit: \(singleBarViewModelData.quantity) \(quantityUnit)", + currencyUnitText: "Currency unit: BDT(৳)", + dataSource: "Data collected from ChalDal.com", + lineChartDataList: lineChartDataModelList + ) + ) + } + private func populateData() { - let rows = csvData.rows + let rows = singleBarViewModelData.csvData.rows barChartData.removeAll() + lineChartDataModelList.removeAll() quantityUnit = "" + var index = 0 for row in rows { let name = row["product_name"] let quantityValue = Double(row["weight_value"] ?? "0") ?? 0 let price = Int(row["price"] ?? "0") ?? 0 let dateStr = DateFormatter.dayMonthYear.date(from: row["date"] ?? "") - if let date = dateStr, name == productName, quantity == quantityValue { + if let date = dateStr, name == singleBarViewModelData.productName, singleBarViewModelData.quantity == quantityValue { let dataName = DateFormatter.dayMonthYearShort.string(from: date) barChartData.append(BarChartData(name: dataName, value: Double(price))) + lineChartDataModelList.append( + .init( + id: index, + xTimeValue: date, + yValue: Double(price) + ) + ) + index += 1 if quantityUnit.isEmpty { quantityUnit = row["weight_unit"] ?? "" } diff --git a/visualization/app/VisualReport/VisualReport/ProductSelection/ProductSelectionScreen.swift b/visualization/app/VisualReport/VisualReport/ProductSelection/ProductSelectionScreen.swift index 7a4db2e8..2c20aede 100644 --- a/visualization/app/VisualReport/VisualReport/ProductSelection/ProductSelectionScreen.swift +++ b/visualization/app/VisualReport/VisualReport/ProductSelection/ProductSelectionScreen.swift @@ -11,6 +11,7 @@ struct ProductSelectionScreen: View { @StateObject private var viewModel: ProductSelectionViewModel @State private var showDetails = false + @Binding private var navigationPath: NavigationPath private var nameView: some View { ScrollView(.vertical) { @@ -18,6 +19,15 @@ struct ProductSelectionScreen: View { Button( action: { showDetails = viewModel.selectProduct(givenName: data.name) + if showDetails { + navigationPath.append( + SingleBarViewModelData( + csvData: viewModel.csvData, + productName: viewModel.selectedName, + quantity: viewModel.selectedQuantity ?? 0.0 + ) + ) + } }, label: { HStack(spacing: 16.0) { @@ -34,46 +44,17 @@ struct ProductSelectionScreen: View { } } - private var detailsView: some View { - SingleBarScreen( - viewModel: SingleBarViewModel( - csvData: viewModel.csvData, - productName: viewModel.selectedName, - quantity: viewModel.selectedQuantity ?? 0.0 - ) - ) - } - - private var backButton: some View { - HStack { - Button( - action: { - showDetails.toggle() - }, - label: { - Text("< Back") - .font(.system(size: 16.0)) - } - ) - Spacer() - } - .padding([.top, .leading]) - } - var body: some View { VStack(alignment: .leading) { - if showDetails { - backButton - detailsView - } nameView - .opacity(showDetails ? 0.0 : 1.0 ) } + .navigationTitle("Select Product") .padding() } - init(viewModel: ProductSelectionViewModel) { + init(viewModel: ProductSelectionViewModel, navigationPath: Binding) { _viewModel = StateObject(wrappedValue: viewModel) + _navigationPath = navigationPath } }