Skip to content

Commit

Permalink
Updated portfolio balance chart
Browse files Browse the repository at this point in the history
* Compute the data series in a background thread
* Used shared PerformanceIndex to setup data series

Issue: #3969
  • Loading branch information
buchen committed May 9, 2024
1 parent db3d7a7 commit bfaa3d9
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.ToolBarManager;
Expand All @@ -18,16 +21,16 @@
import org.swtchart.ILineSeries;
import org.swtchart.ISeries;

import com.google.common.base.Objects;

import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.Portfolio;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Transaction;
import name.abuchen.portfolio.money.CurrencyConverter;
import name.abuchen.portfolio.money.CurrencyConverterImpl;
import name.abuchen.portfolio.money.ExchangeRateProviderFactory;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.snapshot.PerformanceIndex;
import name.abuchen.portfolio.snapshot.PortfolioSnapshot;
import name.abuchen.portfolio.snapshot.filter.ReadOnlyClient;
import name.abuchen.portfolio.ui.Images;
import name.abuchen.portfolio.ui.Messages;
Expand All @@ -45,7 +48,6 @@ public class PortfolioBalanceChart extends TimelineChart // NOSONAR
private Portfolio portfolio;
private ExchangeRateProviderFactory exchangeRateProviderFactory;

private int swtAntialias = SWT.ON;
private EnumSet<ChartDetails> chartConfig = EnumSet.of(ChartDetails.ABSOLUTE_INVESTED_CAPITAL,
ChartDetails.ABSOLUTE_DELTA);

Expand All @@ -66,7 +68,6 @@ public PortfolioBalanceChart(Composite parent, Client client)
ILegend legend = getLegend();
legend.setPosition(SWT.BOTTOM);
legend.setVisible(true);
redraw();
}

private final void readChartConfig(Client client)
Expand All @@ -78,75 +79,124 @@ private final void readChartConfig(Client client)
chartConfig.clear();
for (String key : pref.split(",")) //$NON-NLS-1$
{
chartConfig.add(ChartDetails.valueOf(key));
try
{
chartConfig.add(ChartDetails.valueOf(key));
}
catch (IllegalArgumentException ignore)
{
// ignore unknown configuration parameters
}
}
}

public void updateChart(Portfolio portfolio, ExchangeRateProviderFactory exchangeRateProviderFactory)
{
var isNewPortfolio = !Objects.equal(this.portfolio, portfolio);

this.portfolio = portfolio;
this.exchangeRateProviderFactory = exchangeRateProviderFactory;
getTitle().setText(portfolio.getName());
updateChart();

// unless we are updating an existing portfolio (for example adding or
// removing a data series), we clear the chart as to not show wrong data
// series data
if (isNewPortfolio)
clearChart();

computeAndUpdateChart();
}

public void updateChart()
private void clearChart()
{
try
{
suspendUpdate(true);

for (ISeries s : getSeriesSet().getSeries())
getSeriesSet().deleteSeries(s.getId());
}

if (portfolio == null)
return;
finally
{
adjustRange();
suspendUpdate(false);
}
}

List<PortfolioTransaction> tx = portfolio.getTransactions();
private void computeAndUpdateChart()
{
new Job(PortfolioBalanceChart.class.getName())
{
@Override
protected IStatus run(IProgressMonitor monitor)
{
computeChart();
return Status.OK_STATUS;
}
}.schedule();
}

if (tx.isEmpty())
return;
private void computeChart()
{
List<PortfolioTransaction> tx = portfolio.getTransactions();

Collections.sort(tx, Transaction.BY_DATE);
if (tx.isEmpty())
return;

LocalDate now = LocalDate.now();
LocalDate start = tx.get(0).getDateTime().toLocalDate();
LocalDate end = tx.get(tx.size() - 1).getDateTime().toLocalDate();
LocalDate now = LocalDate.now();
LocalDate start = tx.get(0).getDateTime().toLocalDate();
LocalDate end = tx.get(tx.size() - 1).getDateTime().toLocalDate();

CurrencyConverter converter = new CurrencyConverterImpl(exchangeRateProviderFactory,
client.getBaseCurrency());
if (now.isAfter(end))
end = now;
if (now.isBefore(start))
start = now;

if (now.isAfter(end))
end = now;
if (now.isBefore(start))
start = now;
CurrencyConverter converter = new CurrencyConverterImpl(exchangeRateProviderFactory, client.getBaseCurrency());

int days = (int) ChronoUnit.DAYS.between(start, end) + 2;
var warnings = new ArrayList<Exception>();

// Disable SWT antialias for more than 1000 records due to SWT
// performance issue in Drawing
swtAntialias = days > 1000 ? SWT.OFF : SWT.ON;
var index = PerformanceIndex.forPortfolio(client, converter, portfolio, Interval.of(start, end), warnings);

LocalDate[] dates = new LocalDate[days];
double[] values = new double[days];
Display.getDefault().asyncExec(() -> updateChart(index));
}

dates[0] = start.minusDays(1);
values[0] = 0d;
private void updateChart(PerformanceIndex index)
{
try
{
suspendUpdate(true);

for (int ii = 1; ii < dates.length; ii++)
{
values[ii] = PortfolioSnapshot.create(portfolio, converter, start) //
.getValue().getAmount() / Values.Amount.divider();
dates[ii] = start;
start = start.plusDays(1);
}
for (ISeries s : getSeriesSet().getSeries())
getSeriesSet().deleteSeries(s.getId());

int days = (int) ChronoUnit.DAYS.between(index.getDates()[0],
index.getDates()[index.getDates().length - 1]);

// Disable SWT antialias for more than 1000 records due
// to SWT performance issue in Drawing
var swtAntialias = days > 1000 ? SWT.OFF : SWT.ON;

ILineSeries lineSeries = addDateSeries(portfolio.getUUID(), dates, values, Colors.CASH,
portfolio.getName());
// reverse the order

ILineSeries lineSeries = addDateSeries(portfolio.getUUID(), index.getDates(),
toDouble(index.getTotals(), Values.Amount.divider()), Colors.CASH, portfolio.getName());
lineSeries.setAntialias(swtAntialias);
addChartCommon(dates, converter);
}

if (chartConfig.contains(ChartDetails.ABSOLUTE_INVESTED_CAPITAL))
addAbsoluteInvestedCapital(index, swtAntialias);

if (chartConfig.contains(ChartDetails.ABSOLUTE_DELTA))
addAbsoluteDeltaAllRecords(index, swtAntialias);

if (chartConfig.contains(ChartDetails.TAXES_ACCUMULATED))
addTaxes(index, swtAntialias);

if (chartConfig.contains(ChartDetails.FEES_ACCUMULATED))
addFees(index, swtAntialias);

}
finally
{
adjustRange();
Expand Down Expand Up @@ -180,79 +230,47 @@ private Action addMenuAction(ChartDetails detail)
ReadOnlyClient.unwrap(client).setProperty(PREF_KEY, String.join(",", //$NON-NLS-1$
chartConfig.stream().map(ChartDetails::name).toList()));

updateChart();

computeAndUpdateChart();
});

action.setChecked(chartConfig.contains(detail));
return action;
}

private void addChartCommon(LocalDate[] dates, CurrencyConverter converter)
{
if (chartConfig.contains(ChartDetails.ABSOLUTE_INVESTED_CAPITAL))
addAbsoluteInvestedCapital(dates, converter);

if (chartConfig.contains(ChartDetails.ABSOLUTE_DELTA))
addAbsoluteDeltaAllRecords(dates, converter);

if (chartConfig.contains(ChartDetails.TAXES_ACCUMULATED))
addTaxes(dates, converter);

if (chartConfig.contains(ChartDetails.FEES_ACCUMULATED))
addFees(dates, converter);
}

private void addAbsoluteInvestedCapital(LocalDate[] dates, CurrencyConverter converter)
private void addAbsoluteInvestedCapital(PerformanceIndex index, int swtAntialias)
{
List<Exception> warnings = new ArrayList<>();
PerformanceIndex index = PerformanceIndex.forPortfolio(client, converter, portfolio,
Interval.of(dates[0], dates[dates.length - 1]), warnings);
double[] values;
values = toDouble(index.calculateAbsoluteInvestedCapital(), Values.Amount.divider());
double[] values = toDouble(index.calculateAbsoluteInvestedCapital(), Values.Amount.divider());
String lineID = Messages.LabelAbsoluteInvestedCapital;

ILineSeries lineSeries = addDateSeries(lineID, dates, values, colorAbsoluteInvestedCapital, lineID); // $NON-NLS-1$
ILineSeries lineSeries = addDateSeries(lineID, index.getDates(), values, colorAbsoluteInvestedCapital, lineID);
lineSeries.enableArea(true);
lineSeries.setAntialias(swtAntialias);
}

private void addAbsoluteDeltaAllRecords(LocalDate[] dates, CurrencyConverter converter)
private void addAbsoluteDeltaAllRecords(PerformanceIndex index, int swtAntialias)
{
List<Exception> warnings = new ArrayList<>();
PerformanceIndex index = PerformanceIndex.forPortfolio(client, converter, portfolio,
Interval.of(dates[0], dates[dates.length - 1]), warnings);
double[] values;
values = toDouble(index.calculateAbsoluteDelta(), Values.Amount.divider());
double[] values = toDouble(index.calculateAbsoluteDelta(), Values.Amount.divider());
String lineID = Messages.LabelAbsoluteDelta;

ILineSeries lineSeries = addDateSeries(lineID, dates, values, colorAbsoluteDelta, lineID); // $NON-NLS-1$
ILineSeries lineSeries = addDateSeries(lineID, index.getDates(), values, colorAbsoluteDelta, lineID);
lineSeries.setAntialias(swtAntialias);
}

private void addTaxes(LocalDate[] dates, CurrencyConverter converter)
private void addTaxes(PerformanceIndex index, int swtAntialias)
{
List<Exception> warnings = new ArrayList<>();
PerformanceIndex index = PerformanceIndex.forPortfolio(client, converter, portfolio,
Interval.of(dates[0], dates[dates.length - 1]), warnings);
double[] values;
values = accumulateAndToDouble(index.getTaxes(), Values.Amount.divider());
double[] values = accumulateAndToDouble(index.getTaxes(), Values.Amount.divider());
String lineID = Messages.LabelAccumulatedTaxes;

ILineSeries lineSeries = addDateSeries(lineID, dates, values, colorTaxesAccumulated, lineID); // $NON-NLS-1$
ILineSeries lineSeries = addDateSeries(lineID, index.getDates(), values, colorTaxesAccumulated, lineID);
lineSeries.setAntialias(swtAntialias);
}

private void addFees(LocalDate[] dates, CurrencyConverter converter)
private void addFees(PerformanceIndex index, int swtAntialias)
{
List<Exception> warnings = new ArrayList<>();
PerformanceIndex index = PerformanceIndex.forPortfolio(client, converter, portfolio,
Interval.of(dates[0], dates[dates.length - 1]), warnings);
double[] values;
values = accumulateAndToDouble(index.getFees(), Values.Amount.divider());
double[] values = accumulateAndToDouble(index.getFees(), Values.Amount.divider());
String lineID = Messages.LabelFeesAccumulated;

ILineSeries lineSeries = addDateSeries(lineID, dates, values, colorFeesAccumulated, lineID); // $NON-NLS-1$
ILineSeries lineSeries = addDateSeries(lineID, index.getDates(), values, colorFeesAccumulated, lineID);
lineSeries.setAntialias(swtAntialias);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void onDiscreedModeChanged(@UIEventTopic(UIConstants.Event.Global.DISCREE
@Override
public String getLabel()
{
return Messages.TabAccountBalanceChart;
return Messages.ClientEditorLabelChart;
}

@Override
Expand Down

0 comments on commit bfaa3d9

Please sign in to comment.