BarGraph.swift (4719B)
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 tenTransactions: [TalerTransaction] = Array(slice) 75 let maxValue = maxValue(tenTransactions) 76 77 HStack(alignment: .center, spacing: 1) { 78 if !slice.isEmpty { 79 ForEach(tenTransactions, id: \.self) {transaction in 80 let common = transaction.common 81 let incoming = common.isIncoming 82 let netto = common.amountEffective.value 83 let valueColored = barHeight * netto / maxValue 84 let valueTransparent = barHeight - valueColored 85 // let _ = print("max: \(maxValue), ", incoming ? "+" : "-", netto) 86 VStack(spacing: 0) { 87 let width = barHeight / 3 88 let topHeight = incoming ? valueTransparent : barHeight 89 let botHeight = incoming ? barHeight : valueTransparent 90 if topHeight > 0 { 91 Rectangle() 92 .opacity(INVISIBLE) 93 .frame(width: width, height: topHeight) 94 } 95 Rectangle() 96 .foregroundColor(incoming ? WalletColors().positive : WalletColors().negative) 97 .frame(width: width, height: valueColored) 98 if botHeight > 0 { 99 Rectangle() 100 .opacity(INVISIBLE) 101 .frame(width: width, height: botHeight) 102 } 103 } 104 } 105 } 106 } 107 .accessibilityHidden(true) // cannot speak out this bar chart info 108 .flippedDirection() // draw first array item on trailing edge 109 } 110 } 111 112 113 114 #if false 115 #Preview { 116 var sampleBars: [BarData] { 117 var tempBars = [BarData]() 118 119 for _ in 1...8 { 120 let rand = Double.random(in: -100.0...100.0) 121 122 let bar = BarData(value: rand) 123 tempBars.append(bar) 124 } 125 return tempBars 126 } 127 128 return BarGraph(bars: sampleBars) 129 } 130 #endif