Compare commits

..

3 Commits

5 changed files with 80 additions and 6 deletions

View File

@ -2,6 +2,7 @@ package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.view.component.module.TotalAssetsGraphModule;
import com.andrewlalis.perfin.view.component.module.*;
import javafx.fxml.FXML;
import javafx.geometry.Bounds;
@ -30,7 +31,10 @@ public class DashboardController implements RouteSelectionListener {
var m4 = new VendorSpendChartModule(modulesFlowPane);
m4.columnsProperty.set(2);
modulesFlowPane.getChildren().addAll(accountsModule, transactionsModule, m3, m4);
var m5 = new TotalAssetsGraphModule(modulesFlowPane);
m5.columnsProperty.set(1);
modulesFlowPane.getChildren().addAll(accountsModule, transactionsModule, m3, m4, m5);
}
@Override

View File

@ -9,6 +9,7 @@ import javafx.application.Platform;
import java.math.BigDecimal;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@ -111,15 +112,16 @@ public interface DataSource {
/**
* Gets a list of combined total assets for each currency that's tracked,
* ordered with highest assets first.
* @param timestamp The timestamp at which to get the balance.
* @return A future that resolves to the list of amounts for each currency.
*/
default CompletableFuture<List<MoneyValue>> getCombinedAccountBalances() {
default CompletableFuture<List<MoneyValue>> getCombinedAccountBalances(Instant timestamp) {
return mapRepoAsync(AccountRepository.class, repo -> {
List<Account> accounts = repo.findAll(PageRequest.unpaged()).items();
Map<Currency, BigDecimal> totals = new HashMap<>();
for (var account : accounts) {
BigDecimal currencyTotal = totals.computeIfAbsent(account.getCurrency(), c -> BigDecimal.ZERO);
BigDecimal accountBalance = repo.deriveCurrentBalance(account.id);
BigDecimal accountBalance = repo.deriveBalance(account.id, timestamp);
if (account.getType() == AccountType.CREDIT_CARD) accountBalance = accountBalance.negate();
totals.put(account.getCurrency(), currencyTotal.add(accountBalance));
}
@ -131,4 +133,8 @@ public interface DataSource {
return values;
});
}
default CompletableFuture<List<MoneyValue>> getCombinedAccountBalances() {
return getCombinedAccountBalances(Instant.now());
}
}

View File

@ -52,6 +52,7 @@ public abstract class PieChartModule extends DashboardModule {
this.timeRangeChoiceBox.getItems().addAll(RANGE_CHOICES);
this.timeRangeChoiceBox.getSelectionModel().select("All Time");
this.currencyChoiceBox.managedProperty().bind(this.currencyChoiceBox.visibleProperty());
PieChart chart = new PieChart(chartData);
chart.setLegendVisible(false);
@ -68,9 +69,7 @@ public abstract class PieChartModule extends DashboardModule {
chartData.clear();
}
});
timeRangeChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> {
renderChart();
});
timeRangeChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> renderChart());
}
@Override
@ -136,6 +135,7 @@ public abstract class PieChartModule extends DashboardModule {
} else {
currencyChoiceBox.getSelectionModel().selectFirst();
}
currencyChoiceBox.setVisible(orderedCurrencies.size() > 1);
});
});
}

View File

@ -0,0 +1,63 @@
package com.andrewlalis.perfin.view.component.module;
import com.andrewlalis.perfin.model.Profile;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.*;
import javafx.scene.layout.Pane;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* A module for visualizing the total asset value in the user's profile over
* a configurable period of time.
*/
public class TotalAssetsGraphModule extends DashboardModule {
private final ObservableList<XYChart.Data<String, Number>> totalAssetDataPoints = FXCollections.observableArrayList();
public TotalAssetsGraphModule(Pane parent) {
super(parent);
Axis<String> xAxis = new CategoryAxis();
Axis<Number> yAxis = new NumberAxis();
LineChart<String, Number> chart = new LineChart<>(xAxis, yAxis, FXCollections.observableArrayList(
new XYChart.Series<>("Total Assets", totalAssetDataPoints)
));
chart.setLegendVisible(false);
this.getChildren().add(new ModuleHeader(
"Total Assets over Time"
));
this.getChildren().add(chart);
}
@Override
public void refreshContents() {
totalAssetDataPoints.clear();
String[] dateLabels = new String[12];
double[] values = new double[12];
CompletableFuture<?>[] futures = new CompletableFuture[12];
for (int i = 0; i < 12; i++) {
final int idx = i;
Instant timestamp = Instant.now().minus((12 - i - 1) * 30, ChronoUnit.DAYS);
dateLabels[i] = LocalDate.from(timestamp.atZone(ZoneId.systemDefault())).toString();
futures[i] = Profile.getCurrent().dataSource().getCombinedAccountBalances(timestamp)
.thenAccept(moneyValues -> {
values[idx] = moneyValues.getFirst().amount().doubleValue();
});
}
CompletableFuture.allOf(futures).thenRun(() -> {
List<XYChart.Data<String, Number>> dataPoints = new ArrayList<>(12);
for (int i = 0; i < 12; i++) {
dataPoints.add(new XYChart.Data<>(dateLabels[i], values[i]));
}
Platform.runLater(() -> totalAssetDataPoints.addAll(dataPoints));
});
}
}

View File

@ -20,4 +20,5 @@ module com.andrewlalis.perfin {
opens com.andrewlalis.perfin.view.component to javafx.fxml;
opens com.andrewlalis.perfin.view.component.validation to javafx.fxml;
exports com.andrewlalis.perfin.model.history to javafx.graphics;
opens com.andrewlalis.perfin.view.component.module to javafx.fxml;
}