Added timestamp ranges to the dashboard pie charts.
This commit is contained in:
parent
5a339cbee6
commit
fb2b8d933b
|
@ -2,15 +2,35 @@ package com.andrewlalis.perfin.data;
|
|||
|
||||
import com.andrewlalis.perfin.data.util.DateUtil;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
public record TimestampRange(LocalDateTime start, LocalDateTime end) {
|
||||
public static TimestampRange nDaysTillNow(int days) {
|
||||
public static TimestampRange lastNDays(int days) {
|
||||
LocalDateTime now = DateUtil.nowAsUTC();
|
||||
return new TimestampRange(now.minusDays(days), now);
|
||||
}
|
||||
|
||||
public static TimestampRange thisMonth() {
|
||||
LocalDateTime localStartOfMonth = LocalDate.now(ZoneId.systemDefault()).atStartOfDay().withDayOfMonth(1);
|
||||
LocalDateTime utcStart = localStartOfMonth.atZone(ZoneId.systemDefault())
|
||||
.withZoneSameInstant(ZoneOffset.UTC)
|
||||
.toLocalDateTime();
|
||||
return new TimestampRange(utcStart, DateUtil.nowAsUTC());
|
||||
}
|
||||
|
||||
public static TimestampRange thisYear() {
|
||||
LocalDateTime utcStart = LocalDate.now(ZoneId.systemDefault())
|
||||
.withDayOfYear(1)
|
||||
.atStartOfDay()
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.withZoneSameInstant(ZoneOffset.UTC)
|
||||
.toLocalDateTime();
|
||||
return new TimestampRange(utcStart, DateUtil.nowAsUTC());
|
||||
}
|
||||
|
||||
public static TimestampRange unbounded() {
|
||||
LocalDateTime now = DateUtil.nowAsUTC();
|
||||
return new TimestampRange(LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC), now);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.andrewlalis.perfin.view.component.module;
|
||||
|
||||
import com.andrewlalis.perfin.data.AccountRepository;
|
||||
import com.andrewlalis.perfin.data.TimestampRange;
|
||||
import com.andrewlalis.perfin.data.util.ColorUtil;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import javafx.application.Platform;
|
||||
|
@ -13,30 +14,92 @@ import javafx.scene.paint.Color;
|
|||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An abstract dashboard module for displaying a pie chart of data based on a
|
||||
* selected currency context.
|
||||
* selected currency context and time window.
|
||||
*/
|
||||
public abstract class PieChartModule extends DashboardModule {
|
||||
private static final Map<String, Supplier<TimestampRange>> TIMESTAMP_RANGES = Map.of(
|
||||
"Last 7 days", () -> TimestampRange.lastNDays(7),
|
||||
"Last 30 days", () -> TimestampRange.lastNDays(30),
|
||||
"Last 90 days", () -> TimestampRange.lastNDays(90),
|
||||
"This Month", TimestampRange::thisMonth,
|
||||
"This Year", TimestampRange::thisYear,
|
||||
"All Time", TimestampRange::unbounded
|
||||
);
|
||||
private static final String[] RANGE_CHOICES = {
|
||||
"Last 7 days",
|
||||
"Last 30 days",
|
||||
"Last 90 days",
|
||||
"This Month",
|
||||
"This Year",
|
||||
"All Time"
|
||||
};
|
||||
|
||||
private final ObservableList<PieChart.Data> chartData = FXCollections.observableArrayList();
|
||||
protected final List<Color> dataColors = new ArrayList<>();
|
||||
private final ChoiceBox<Currency> currencyChoiceBox = new ChoiceBox<>();
|
||||
private final ChoiceBox<String> timeRangeChoiceBox = new ChoiceBox<>();
|
||||
private final String preferredCurrencySetting;
|
||||
private final String timeRangeSetting;
|
||||
|
||||
public PieChartModule(Pane parent, String title, String preferredCurrencySetting) {
|
||||
public PieChartModule(Pane parent, String title, String preferredCurrencySetting, String timeRangeSetting) {
|
||||
super(parent);
|
||||
this.preferredCurrencySetting = preferredCurrencySetting;
|
||||
this.timeRangeSetting = timeRangeSetting;
|
||||
|
||||
this.timeRangeChoiceBox.getItems().addAll(RANGE_CHOICES);
|
||||
this.timeRangeChoiceBox.getSelectionModel().select("All Time");
|
||||
|
||||
PieChart chart = new PieChart(chartData);
|
||||
chart.setLegendVisible(false);
|
||||
this.getChildren().add(new ModuleHeader(
|
||||
title,
|
||||
timeRangeChoiceBox,
|
||||
currencyChoiceBox
|
||||
));
|
||||
this.getChildren().add(chart);
|
||||
currencyChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
getChartData(newValue).exceptionally(throwable -> {
|
||||
renderChart();
|
||||
} else {
|
||||
chartData.clear();
|
||||
}
|
||||
});
|
||||
timeRangeChoiceBox.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
renderChart();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshContents() {
|
||||
refreshCurrencies();
|
||||
String savedTimeRangeLabel = Profile.getCurrent().getSetting(timeRangeSetting).orElse(null);
|
||||
if (savedTimeRangeLabel != null && TIMESTAMP_RANGES.containsKey(savedTimeRangeLabel)) {
|
||||
timeRangeChoiceBox.getSelectionModel().select(savedTimeRangeLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private TimestampRange getSelectedTimestampRange() {
|
||||
String selectedLabel = timeRangeChoiceBox.getValue();
|
||||
if (selectedLabel == null || !TIMESTAMP_RANGES.containsKey(selectedLabel)) {
|
||||
return TimestampRange.unbounded();
|
||||
}
|
||||
return TIMESTAMP_RANGES.get(selectedLabel).get();
|
||||
}
|
||||
|
||||
private void renderChart() {
|
||||
final Currency currency = currencyChoiceBox.getValue();
|
||||
String timeRangeLabel = timeRangeChoiceBox.getValue();
|
||||
if (currency == null || timeRangeLabel == null) {
|
||||
chartData.clear();
|
||||
dataColors.clear();
|
||||
return;
|
||||
}
|
||||
final TimestampRange range = getSelectedTimestampRange();
|
||||
getChartData(currency, range).exceptionally(throwable -> {
|
||||
throwable.printStackTrace(System.err);
|
||||
return Collections.emptyList();
|
||||
})
|
||||
|
@ -49,16 +112,8 @@ public abstract class PieChartModule extends DashboardModule {
|
|||
}
|
||||
}
|
||||
}));
|
||||
Profile.getCurrent().setSettingAndSave(preferredCurrencySetting, newValue.getCurrencyCode());
|
||||
} else {
|
||||
chartData.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshContents() {
|
||||
refreshCurrencies();
|
||||
Profile.getCurrent().setSettingAndSave(preferredCurrencySetting, currency.getCurrencyCode());
|
||||
Profile.getCurrent().setSettingAndSave(timeRangeSetting, timeRangeLabel);
|
||||
}
|
||||
|
||||
private void refreshCurrencies() {
|
||||
|
@ -85,5 +140,5 @@ public abstract class PieChartModule extends DashboardModule {
|
|||
});
|
||||
}
|
||||
|
||||
protected abstract CompletableFuture<List<PieChart.Data>> getChartData(Currency currency);
|
||||
protected abstract CompletableFuture<List<PieChart.Data>> getChartData(Currency currency, TimestampRange range);
|
||||
}
|
||||
|
|
|
@ -17,13 +17,18 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
public class SpendingCategoryChartModule extends PieChartModule {
|
||||
public SpendingCategoryChartModule(Pane parent) {
|
||||
super(parent, "Spending by Category", "charts.category-spend.default-currency");
|
||||
super(
|
||||
parent,
|
||||
"Spending by Category",
|
||||
"charts.category-spend.default-currency",
|
||||
"charts.category-spend.default-time-range"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<List<PieChart.Data>> getChartData(Currency currency) {
|
||||
protected CompletableFuture<List<PieChart.Data>> getChartData(Currency currency, TimestampRange range) {
|
||||
return Profile.getCurrent().dataSource().mapRepoAsync(AnalyticsRepository.class, repo -> {
|
||||
var data = repo.getSpendByRootCategory(TimestampRange.unbounded(), currency);
|
||||
var data = repo.getSpendByRootCategory(range, currency);
|
||||
dataColors.clear();
|
||||
return data.stream()
|
||||
.map(pair -> {
|
||||
|
|
|
@ -16,13 +16,18 @@ import java.util.concurrent.CompletableFuture;
|
|||
|
||||
public class VendorSpendChartModule extends PieChartModule {
|
||||
public VendorSpendChartModule(Pane parent) {
|
||||
super(parent, "Spending by Vendor", "charts.vendor-spend.default-currency");
|
||||
super(
|
||||
parent,
|
||||
"Spending by Vendor",
|
||||
"charts.vendor-spend.default-currency",
|
||||
"charts.vendor-spend.default-time-range"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<List<PieChart.Data>> getChartData(Currency currency) {
|
||||
protected CompletableFuture<List<PieChart.Data>> getChartData(Currency currency, TimestampRange range) {
|
||||
return Profile.getCurrent().dataSource().mapRepoAsync(AnalyticsRepository.class, repo -> {
|
||||
var data = repo.getSpendByVendor(TimestampRange.unbounded(), currency);
|
||||
var data = repo.getSpendByVendor(range, currency);
|
||||
return data.stream()
|
||||
.map(pair -> {
|
||||
TransactionVendor vendor = pair.first();
|
||||
|
|
Loading…
Reference in New Issue