jgnash.ui.report.compiled.MonthlyAccountBalanceChartCompare.java Source code

Java tutorial

Introduction

Here is the source code for jgnash.ui.report.compiled.MonthlyAccountBalanceChartCompare.java

Source

/*
 * jGnash, a personal finance application
 * Copyright (C) 2001-2016 Craig Cavanaugh
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jgnash.ui.report.compiled;

import java.awt.EventQueue;
import java.awt.event.ActionListener;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JPanel;

import jgnash.engine.Account;
import jgnash.engine.AccountType;
import jgnash.engine.Comparators;
import jgnash.engine.CurrencyNode;
import jgnash.ui.components.DatePanel;
import jgnash.ui.components.FilteredAccountListComboBox;
import jgnash.ui.components.GenericCloseDialog;
import jgnash.ui.register.AccountBalanceDisplayManager;
import jgnash.ui.util.IconUtils;
import jgnash.util.DateUtils;
import jgnash.util.ResourceUtils;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.RowSpec;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.DateTickUnitType;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.ClusteredXYBarRenderer;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

/**
 * This displays a dialog that shows the monthly balance for a selected account
 *
 * @author Craig Cavanaugh
 * @author Dany Veilleux
 * @author Peter Vida
 * @author Pranay Kumar
 */

/**
 * Modified by Pranay Kumar - Compare accounts on the same chart - Show target currency on the chart axis label (must
 * for people working with multiple currencies) - Use same code for recursive and non recursive account balance
 * calculations
 */

public class MonthlyAccountBalanceChartCompare {

    private final FilteredAccountListComboBox combo1 = new FilteredAccountListComboBox(false, false);

    private final FilteredAccountListComboBox combo2 = new FilteredAccountListComboBox(false, false);

    //this is a customisation that can be removed for a more general release

    private final ResourceBundle rb = ResourceUtils.getBundle();

    private final DatePanel startDateField = new DatePanel();

    private final DatePanel endDateField = new DatePanel();

    private JCheckBox subAccountCheckBox;

    private JCheckBox hideLockedAccountCheckBox;

    private JCheckBox hidePlaceholderAccountCheckBox;

    private JCheckBox jcb_compare;

    public static void show() {

        EventQueue.invokeLater(() -> {
            MonthlyAccountBalanceChartCompare chart = new MonthlyAccountBalanceChartCompare();

            JPanel p = chart.createPanel();
            GenericCloseDialog d = new GenericCloseDialog(p, ResourceUtils.getString("Title.AccountBalance"));
            d.pack();
            d.setModal(false);

            d.setVisible(true);
        });
    }

    private JPanel createPanel() {
        LocalDate end = DateUtils.getLastDayOfTheMonth(endDateField.getLocalDate());
        LocalDate start = end.minusYears(1);

        startDateField.setDate(start);

        JButton refreshButton = new JButton(rb.getString("Button.Refresh"));
        refreshButton.setIcon(IconUtils.getIcon("/jgnash/resource/view-refresh.png"));

        subAccountCheckBox = new JCheckBox(rb.getString("Button.IncludeSubAccounts"));
        subAccountCheckBox.setSelected(true);

        hideLockedAccountCheckBox = new JCheckBox(rb.getString("Button.HideLockedAccount"));
        hidePlaceholderAccountCheckBox = new JCheckBox(rb.getString("Button.HidePlaceholderAccount"));

        jcb_compare = new JCheckBox(rb.getString("Button.Compare"));
        jcb_compare.setSelected(true);

        Account a = combo1.getSelectedAccount();
        Account a2 = combo2.getSelectedAccount();

        JFreeChart chart = createVerticalXYBarChart(a, a2);
        final ChartPanel chartPanel = new ChartPanel(chart);

        FormLayout layout = new FormLayout("p, 4dlu, p:g", "");
        DefaultFormBuilder builder = new DefaultFormBuilder(layout);

        FormLayout dLayout = new FormLayout("p, 4dlu, p, 8dlu, p, 4dlu, p, 8dlu, p", "");
        DefaultFormBuilder dBuilder = new DefaultFormBuilder(dLayout);

        dBuilder.append(rb.getString("Label.StartDate"), startDateField);
        dBuilder.append(rb.getString("Label.EndDate"), endDateField);
        dBuilder.append(refreshButton);

        FormLayout cbLayout = new FormLayout("p, 4dlu, p, 4dlu, p, 4dlu", "");
        DefaultFormBuilder cbBuilder = new DefaultFormBuilder(cbLayout);

        cbBuilder.append(subAccountCheckBox);
        cbBuilder.append(hideLockedAccountCheckBox);
        cbBuilder.append(hidePlaceholderAccountCheckBox);

        builder.append(rb.getString("Label.Account"), combo1);
        builder.nextLine();
        builder.append(rb.getString("Label.Compare"), combo2);
        builder.nextLine();
        builder.append(jcb_compare);
        builder.append(cbBuilder.getPanel());
        builder.nextLine();
        builder.appendRelatedComponentsGapRow();
        builder.nextLine();
        builder.append(dBuilder.getPanel(), 3);
        builder.nextLine();
        builder.appendUnrelatedComponentsGapRow();
        builder.nextLine();

        builder.appendRow(RowSpec.decode("fill:p:g"));
        builder.append(chartPanel, 3);

        final JPanel panel = builder.getPanel();

        ActionListener listener = e -> {
            try {
                if (e.getSource() == jcb_compare) {
                    combo2.setEnabled(jcb_compare.isSelected());
                }

                Account account = combo1.getSelectedAccount();
                if (account == null) {
                    return;
                }

                Account account2 = combo2.getSelectedAccount();
                if (jcb_compare.isSelected() && account2 == null) {
                    return;
                }

                updateSubAccountBox();

                chartPanel.setChart(createVerticalXYBarChart(account, account2));
                panel.validate();
            } catch (final Exception ex) {
                Logger.getLogger(MonthlyAccountBalanceChartCompare.class.getName()).log(Level.SEVERE,
                        ex.getLocalizedMessage(), ex);
            }
        };

        combo1.addActionListener(listener);
        combo2.addActionListener(listener);
        jcb_compare.addActionListener(listener);
        subAccountCheckBox.addActionListener(listener);

        hideLockedAccountCheckBox.addActionListener(e -> {
            combo1.setHideLocked(hideLockedAccountCheckBox.isSelected());
            combo2.setHideLocked(hideLockedAccountCheckBox.isSelected());
            try {
                Account account = combo1.getSelectedAccount();
                if (account == null) {
                    return;
                }

                Account account2 = combo2.getSelectedAccount();
                if (jcb_compare.isSelected() && account2 == null) {
                    return;
                }

                updateSubAccountBox();

                chartPanel.setChart(createVerticalXYBarChart(account, account2));
                panel.validate();
            } catch (final Exception ex) {
                Logger.getLogger(MonthlyAccountBalanceChartCompare.class.getName()).log(Level.SEVERE,
                        ex.getLocalizedMessage(), ex);
            }
        });

        hidePlaceholderAccountCheckBox.addActionListener(e -> {
            combo1.setHidePlaceholder(hidePlaceholderAccountCheckBox.isSelected());
            combo2.setHidePlaceholder(hidePlaceholderAccountCheckBox.isSelected());
            try {
                Account account = combo1.getSelectedAccount();
                if (account == null) {
                    return;
                }
                Account account2 = combo2.getSelectedAccount();
                if (jcb_compare.isSelected() && account2 == null) {
                    return;
                }

                updateSubAccountBox();

                chartPanel.setChart(createVerticalXYBarChart(account, account2));
                panel.validate();
            } catch (final Exception ex) {
                Logger.getLogger(MonthlyAccountBalanceChartCompare.class.getName()).log(Level.SEVERE,
                        ex.getLocalizedMessage(), ex);
            }
        });
        refreshButton.addActionListener(listener);

        updateSubAccountBox();

        return panel;
    }

    private void updateSubAccountBox() {
        Account a = combo1.getSelectedAccount();
        if (a == null) {
            return;
        }
        if (a.getChildCount() == 0) {
            Account a2 = combo2.getSelectedAccount();
            if (combo2.isEnabled() && a2 != null && a2.getChildCount() > 0) {
                subAccountCheckBox.setEnabled(true);
            } else {
                subAccountCheckBox.setEnabled(false);
            }
        } else {
            subAccountCheckBox.setEnabled(true);
        }
    }

    private JFreeChart createVerticalXYBarChart(final Account a, final Account a2) {
        DateFormat df = new SimpleDateFormat("MM/yy");
        TimeSeriesCollection data = createTimeSeriesCollection(a, a2);

        DateAxis dateAxis = new DateAxis(rb.getString("Column.Date"));
        dateAxis.setTickUnit(new DateTickUnit(DateTickUnitType.MONTH, 1, df));
        dateAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);

        // if (a.getTransactionCount() > 0) {
        Date start = DateUtils.asDate(DateUtils.getFirstDayOfTheMonth(startDateField.getLocalDate()));
        Date end = DateUtils.asDate(DateUtils.getLastDayOfTheMonth(endDateField.getLocalDate()));
        dateAxis.setRange(start, end);
        // }

        NumberAxis valueAxis = new NumberAxis(
                rb.getString("Column.Balance") + "-" + a.getCurrencyNode().getSymbol());
        StandardXYToolTipGenerator tooltipGenerator = new StandardXYToolTipGenerator("{1}, {2}", df,
                NumberFormat.getNumberInstance());
        ClusteredXYBarRenderer renderer = new ClusteredXYBarRenderer(0.2, false);
        renderer.setBaseToolTipGenerator(tooltipGenerator);

        XYPlot plot = new XYPlot(data, dateAxis, valueAxis, renderer);
        String title;
        if (jcb_compare.isSelected()) {
            title = a.getPathName() + " vs " + a2.getPathName();
        } else {
            title = rb.getString("Title.AccountBalance") + " - " + a.getPathName();
        }

        JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, plot, false);
        chart.setBackgroundPaint(null);

        return chart;
    }

    private TimeSeriesCollection createTimeSeriesCollection(final Account account, final Account a2) {
        //always use this method
        //if (subAccountCheckBox.isApproved()) {
        // Getting the dates to calculate
        LocalDate start = startDateField.getLocalDate().with(TemporalAdjusters.firstDayOfMonth());
        LocalDate stop = endDateField.getLocalDate().with(TemporalAdjusters.lastDayOfMonth());

        List<LocalDate> list = DateUtils.getLastDayOfTheMonths(start, stop);

        TimeSeries t = new TimeSeries(rb.getString("Column.Month"), rb.getString("Column.Month"),
                rb.getString("Column.Balance"));
        TimeSeries t2 = new TimeSeries(rb.getString("Column.Month"), rb.getString("Column.Month"),
                rb.getString("Column.Balance"));

        // For every month, calculate the total amount
        for (final LocalDate localDate : list) {
            final LocalDate d = localDate.with(TemporalAdjusters.lastDayOfMonth());
            final LocalDate s = localDate.with(TemporalAdjusters.firstDayOfMonth());

            // Get the total amount for the account and every sub accounts
            // for the specified date
            //BigDecimal bd_TotalAmount = calculateTotal(s, d, account, account.getCurrencyNode());
            BigDecimal bd_TotalAmount = calculateTotal(s, d, account, subAccountCheckBox.isSelected(),
                    account.getCurrencyNode());

            // Include it in the graph
            t.add(new Month(DateUtils.asDate(localDate)), totalModulus(bd_TotalAmount, account.getAccountType()));
            if (jcb_compare.isSelected()) {
                bd_TotalAmount = calculateTotal(s, d, a2, subAccountCheckBox.isSelected(),
                        account.getCurrencyNode());
                t2.add(new Month(DateUtils.asDate(localDate)), totalModulus(bd_TotalAmount, a2.getAccountType()));
            }
        }

        TimeSeriesCollection tsc = new TimeSeriesCollection();
        tsc.addSeries(t);
        if (jcb_compare.isSelected()) {
            tsc.addSeries(t2);
        }

        return tsc;
        /*
            return new TimeSeriesCollection(t);
        }
            
        int count = account.getTransactionCount();
            
        if (count > 0) {
            Date start = account.getTransactionAt(0).getDate();
            Date stop = account.getTransactionAt(count - 1).getDate();
            list = DateUtils.getLastDayOfTheMonths(start, stop);
        }
            
        TimeSeries t = new TimeSeries(rb.getString("Column.Month"), rb.getString("Column.Month"), rb.getString("Column.Balance"));
            
        AccountType type = account.getAccountType();
            
        for (Date aList : list) {
            // get balance for the whole month
            Date d = DateUtils.getLastDayOfTheMonth(aList);
            Date s = DateUtils.getFirstDayOfTheMonth(aList);
            
            BigDecimal balance = AccountBalanceDisplayManager.convertToSelectedBalanceMode(type, account.getBalance(s, d));
            t.add(new Month(aList), balance);
        }
            
        return new TimeSeriesCollection(t);
        */
    }

    private static BigDecimal calculateTotal(final LocalDate start, final LocalDate end, final Account account,
            boolean recursive, final CurrencyNode baseCurrency) {
        BigDecimal amount;
        AccountType type = account.getAccountType();

        // get the amount for the account                
        amount = AccountBalanceDisplayManager.convertToSelectedBalanceMode(type,
                account.getBalance(start, end, baseCurrency));

        if (recursive) {
            // add the amount of every sub accounts
            for (final Account child : account.getChildren(Comparators.getAccountByCode())) {
                amount = amount.add(calculateTotal(start, end, child, true, baseCurrency));
            }
        }

        return amount;
    }

    private static BigDecimal totalModulus(BigDecimal amount, final AccountType type) {
        if (type == AccountType.INCOME || type == AccountType.EQUITY || type == AccountType.CREDIT
                || type == AccountType.LIABILITY) {
            return amount.negate();
        }

        return amount;
    }

}