BarGraph.swift (4873B)
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 let isLegacy = scope.type == .exchangeLegacyKeys 43 HStack (alignment: .center, spacing: 10) { 44 if !minimalistic || currencyInfo.hasSymbol { 45 Text(currencyInfo.name) 46 .strikethrough(isLegacy, color: WalletColors().attention) 47 .talerFont(.title2) 48 .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast)) 49 } 50 BarGraph(transactions: $completedTransactions, 51 maxBars: MAXBARS, barHeight: barHeight) 52 } 53 // .headerProminence(.increased) // unfortunately this is not useful 54 .task(id: reloadTransactions + 2_000_000) { await loadCompleted() } 55 } 56 } 57 // MARK: - 58 struct BarGraph: View { 59 @Binding var transactions: [TalerTransaction] 60 let maxBars: Int 61 let barHeight: Double 62 63 func maxValue(_ someTransactions: [TalerTransaction]) -> Double { 64 var maxValue = 0.0 65 for transaction in someTransactions { 66 let value = transaction.common.amountEffective.value 67 if value > maxValue { 68 maxValue = value 69 } 70 } 71 return maxValue 72 } 73 74 var body: some View { 75 let slice = transactions.prefix(maxBars) 76 let tenTransactions: [TalerTransaction] = Array(slice) 77 let maxValue = maxValue(tenTransactions) 78 79 HStack(alignment: .center, spacing: 1) { 80 if !slice.isEmpty { 81 ForEach(tenTransactions, id: \.self) {transaction in 82 let common = transaction.common 83 let incoming = common.isIncoming 84 let netto = common.amountEffective.value 85 let valueColored = maxValue > 0 ? barHeight * netto / maxValue : 0 86 let valueTransparent = barHeight - valueColored 87 // let _ = print("max: \(maxValue), ", incoming ? "+" : "-", netto) 88 VStack(spacing: 0) { 89 let width = barHeight / 3 90 let topHeight = incoming ? valueTransparent : barHeight 91 let botHeight = incoming ? barHeight : valueTransparent 92 if topHeight > 0 { 93 Rectangle() 94 .opacity(INVISIBLE) 95 .frame(width: width, height: topHeight) 96 } 97 Rectangle() 98 .foregroundColor(incoming ? WalletColors().positive : WalletColors().negative) 99 .frame(width: width, height: valueColored) 100 if botHeight > 0 { 101 Rectangle() 102 .opacity(INVISIBLE) 103 .frame(width: width, height: botHeight) 104 } 105 } 106 } 107 } 108 } 109 .accessibilityHidden(true) // cannot speak out this bar chart info 110 .flippedDirection() // draw first array item on trailing edge 111 } 112 } 113 114 115 116 #if false 117 #Preview { 118 var sampleBars: [BarData] { 119 var tempBars = [BarData]() 120 121 for _ in 1...8 { 122 let rand = Double.random(in: -100.0...100.0) 123 124 let bar = BarData(value: rand) 125 tempBars.append(bar) 126 } 127 return tempBars 128 } 129 130 return BarGraph(bars: sampleBars) 131 } 132 #endif