BarGraph.swift (4746B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import SymLog 10 11 let MAXBARS = 15 12 13 struct BarGraphHeader: View { 14 private let symLog = SymLogV(0) 15 let stack: CallStack 16 let scope: ScopeInfo 17 @Binding var reloadTransactions: Int 18 19 @EnvironmentObject private var model: WalletModel 20 @EnvironmentObject private var controller: Controller 21 @Environment(\.colorScheme) private var colorScheme 22 @Environment(\.colorSchemeContrast) private var colorSchemeContrast 23 @AppStorage("minimalistic") var minimalistic: Bool = false 24 25 @State private var completedTransactions: [TalerTransaction] = [] 26 @ScaledMetric var barHeight = 9 // relative to fontSize 27 28 @MainActor 29 private func loadCompleted() async { 30 symLog.log(".task for BarGraphHeader(\(scope.currency)) - load \(MAXBARS) Transactions") 31 if let response = try? await model.getTransactionsV2(stack.push("BarGraphHeader - \(scope.url?.trimURL)"), 32 scope: scope, 33 filterByState: .done, 34 limit: MAXBARS 35 ) { 36 completedTransactions = response 37 } 38 } 39 40 var body: some View { 41 let currencyInfo = controller.info(for: scope, controller.currencyTicker) 42 HStack (alignment: .center, spacing: 10) { 43 if !minimalistic || currencyInfo.hasSymbol { 44 Text(currencyInfo.name) 45 .talerFont(.title2) 46 .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 47 } 48 BarGraph(transactions: $completedTransactions, 49 maxBars: MAXBARS, barHeight: barHeight) 50 } 51 // .headerProminence(.increased) // unfortunately this is not useful 52 .task(id: reloadTransactions + 2_000_000) { await loadCompleted() } 53 } 54 } 55 // MARK: - 56 struct BarGraph: View { 57 @Binding var transactions: [TalerTransaction] 58 let maxBars: Int 59 let barHeight: Double 60 61 func maxValue(_ someTransactions: [TalerTransaction]) -> Double { 62 var maxValue = 0.0 63 for transaction in someTransactions { 64 let value = transaction.common.amountEffective.value 65 if value > maxValue { 66 maxValue = value 67 } 68 } 69 return maxValue 70 } 71 72 var body: some View { 73 let slice = transactions.prefix(maxBars) 74 let count = slice.count 75 let tenTransactions: [TalerTransaction] = Array(slice) 76 let maxValue = maxValue(tenTransactions) 77 78 HStack(alignment: .center, spacing: 1) { 79 if count > 0 { 80 ForEach(tenTransactions, id: \.self) {transaction in 81 let common = transaction.common 82 let incoming = common.isIncoming 83 let netto = common.amountEffective.value 84 let valueColored = barHeight * netto / maxValue 85 let valueTransparent = barHeight - valueColored 86 // let _ = print("max: \(maxValue), ", incoming ? "+" : "-", netto) 87 VStack(spacing: 0) { 88 let width = barHeight / 3 89 let topHeight = incoming ? valueTransparent : barHeight 90 let botHeight = incoming ? barHeight : valueTransparent 91 if topHeight > 0 { 92 Rectangle() 93 .opacity(INVISIBLE) 94 .frame(width: width, height: topHeight) 95 } 96 Rectangle() 97 .foregroundColor(incoming ? WalletColors().positive : WalletColors().negative) 98 .frame(width: width, height: valueColored) 99 if botHeight > 0 { 100 Rectangle() 101 .opacity(INVISIBLE) 102 .frame(width: width, height: botHeight) 103 } 104 } 105 } 106 } 107 } 108 .accessibilityHidden(true) // cannot speak out this bar chart info 109 .flippedDirection() // draw first array item on trailing edge 110 } 111 } 112 113 114 115 #if false 116 #Preview { 117 var sampleBars: [BarData] { 118 var tempBars = [BarData]() 119 120 for _ in 1...8 { 121 let rand = Double.random(in: -100.0...100.0) 122 123 let bar = BarData(value: rand) 124 tempBars.append(bar) 125 } 126 return tempBars 127 } 128 129 return BarGraph(bars: sampleBars) 130 } 131 #endif