diff --git a/src/de/deadlocker8/budgetmaster/logic/CategoryInOutSum.java b/src/de/deadlocker8/budgetmaster/logic/CategoryInOutSum.java new file mode 100644 index 0000000000000000000000000000000000000000..4b49c5b10d088b5b6a51ddf7c14a03d82e918c40 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/CategoryInOutSum.java @@ -0,0 +1,82 @@ +package de.deadlocker8.budgetmaster.logic; + +import javafx.scene.paint.Color; + +public class CategoryInOutSum +{ + private int ID; + private String name; + private Color color; + private int budgetIN; + private int budgetOUT; + + public CategoryInOutSum(int ID, String name, Color color, int budgetIN, int budgetOUT) + { + this.ID = ID; + this.name = name; + this.color = color; + this.budgetIN = budgetIN; + this.budgetOUT = budgetOUT; + } + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public Color getColor() + { + return color; + } + + public void setColor(Color color) + { + this.color = color; + } + + public int getBudgetIN() + { + return budgetIN; + } + + public void setBudgetIN(int budgetIN) + { + this.budgetIN = budgetIN; + } + + public int getBudgetOUT() + { + return budgetOUT; + } + + public void setBudgetOUT(int budgetOUT) + { + this.budgetOUT = budgetOUT; + } + + @Override + public boolean equals(Object obj) + { + if(this == obj) + return true; + if(obj == null) + return false; + if(getClass() != obj.getClass()) + return false; + CategoryInOutSum other = (CategoryInOutSum)obj; + if(ID != other.ID) + return false; + return true; + } + + @Override + public String toString() + { + return "CategoryInOutSum [ID=" + ID + ", name=" + name + ", color=" + color + ", budgetIN=" + budgetIN + ", budgetOUT=" + budgetOUT + "]"; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/Helpers.java b/src/de/deadlocker8/budgetmaster/logic/Helpers.java index 46cbd7e69215357f7aad1354fc9e775767fcc054..9190df3577db8a5f28558da7a167bc9e24489ed6 100644 --- a/src/de/deadlocker8/budgetmaster/logic/Helpers.java +++ b/src/de/deadlocker8/budgetmaster/logic/Helpers.java @@ -5,12 +5,16 @@ import java.net.URLEncoder; import java.text.DecimalFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; public class Helpers { public static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("0.00"); - public static final String SALT = "ny9/Y+G|WrJ,82|oIYQQ X %i-sq#4,uA-qKPtwFPnw+s(k2`rV)^-a1|t{D3Z>S"; + public static final String COLOR_INCOME = "#22BAD9"; + public static final String COLOR_PAYMENT = "#F2612D"; + public static final String SALT = "ny9/Y+G|WrJ,82|oIYQQ X %i-sq#4,uA-qKPtwFPnw+s(k2`rV)^-a1|t{D3Z>S"; + public static String getURLEncodedString(String input) { try @@ -22,7 +26,7 @@ public class Helpers return input; } } - + public static String getDateString(LocalDate date) { if(date == null) @@ -32,4 +36,32 @@ public class Helpers DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); return date.format(formatter); } + + public static ArrayList<String> getMonthList() + { + ArrayList<String> monthNames = new ArrayList<>(); + monthNames.add("Januar"); + monthNames.add("Februar"); + monthNames.add("März"); + monthNames.add("April"); + monthNames.add("Mai"); + monthNames.add("Juni"); + monthNames.add("Juli"); + monthNames.add("August"); + monthNames.add("September"); + monthNames.add("Oktober"); + monthNames.add("November"); + monthNames.add("Dezember"); + return monthNames; + } + + public static ArrayList<String> getYearList() + { + ArrayList<String> years = new ArrayList<>(); + for(int i = 2000; i < 2100; i++) + { + years.add(String.valueOf(i)); + } + return years; + } } \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/MonthInOutSum.java b/src/de/deadlocker8/budgetmaster/logic/MonthInOutSum.java new file mode 100644 index 0000000000000000000000000000000000000000..edc3d0f572bd1d87283f6d5dbcb0d56581e53fca --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/MonthInOutSum.java @@ -0,0 +1,67 @@ +package de.deadlocker8.budgetmaster.logic; + +import java.util.ArrayList; + +import org.joda.time.DateTime; + +public class MonthInOutSum +{ + private int month; + private int year; + private ArrayList<CategoryInOutSum> sums; + + public MonthInOutSum(int month, int year, ArrayList<CategoryInOutSum> sums) + { + this.month = month; + this.year = year; + this.sums = sums; + } + + public int getMonth() + { + return month; + } + + public int getYear() + { + return year; + } + + public ArrayList<CategoryInOutSum> getSums() + { + return sums; + } + + public DateTime getDate() + { + return DateTime.now().withYear(year).withMonthOfYear(month).withDayOfMonth(1); + } + + public int getBudgetIN() + { + int budget = 0; + for(CategoryInOutSum currentCategorySum : sums) + { + budget += currentCategorySum.getBudgetIN(); + } + + return budget; + } + + public int getBudgetOUT() + { + int budget = 0; + for(CategoryInOutSum currentCategorySum : sums) + { + budget += currentCategorySum.getBudgetOUT(); + } + + return budget; + } + + @Override + public String toString() + { + return "MonthInOutSum [month=" + month + ", year=" + year + ", sums=" + sums + "]"; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java b/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java index ed2c7b14570da2d3bf05611be5973b04fdfab912..4759378f49b10f740085be73f845fb8e1ff51062 100644 --- a/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java +++ b/src/de/deadlocker8/budgetmaster/logic/ServerConnection.java @@ -13,6 +13,8 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import org.joda.time.DateTime; + import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -421,4 +423,55 @@ public class ServerConnection // throw new ServerConnectionException(String.valueOf(httpsCon.getResponseCode())); // } } + + /* + * CHARTS + */ + public ArrayList<CategoryInOutSum> getCategoryInOutSumForMonth(DateTime startDate, DateTime endDate) throws Exception + { + URL url = new URL(settings.getUrl() + "/charts/categoryInOutSum?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + + "&startDate=" + startDate.toString("yyyy-MM-dd") + + "&endDate=" + endDate.toString("yyyy-MM-dd")); + HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection(); + httpsCon.setDoOutput(true); + httpsCon.setRequestMethod("GET"); + + if(httpsCon.getResponseCode() == HttpsURLConnection.HTTP_OK) + { + String result = Read.getStringFromInputStream(httpsCon.getInputStream()); + // required by GSON + Type listType = new TypeToken<ArrayList<CategoryInOutSum>>() + { + }.getType(); + return gson.fromJson(result, listType); + } + else + { + return null; + } + } + + public ArrayList<MonthInOutSum> getMonthInOutSum(DateTime startDate, DateTime endDate) throws Exception + { + URL url = new URL(settings.getUrl() + "/charts/monthInOutSum?secret=" + Helpers.getURLEncodedString(settings.getSecret()) + + "&startDate=" + startDate.toString("yyyy-MM-dd") + + "&endDate=" + endDate.toString("yyyy-MM-dd")); + HttpsURLConnection httpsCon = (HttpsURLConnection)url.openConnection(); + httpsCon.setDoOutput(true); + httpsCon.setRequestMethod("GET"); + + if(httpsCon.getResponseCode() == HttpsURLConnection.HTTP_OK) + { + String result = Read.getStringFromInputStream(httpsCon.getInputStream()); + // required by GSON + Type listType = new TypeToken<ArrayList<MonthInOutSum>>() + { + }.getType(); + return gson.fromJson(result, listType); + } + else + { + return null; + } + } } \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..c3276887a3f8d70f7770ac4acf9f0b54c1e0fcb3 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/BarChartGenerator.java @@ -0,0 +1,129 @@ +package de.deadlocker8.budgetmaster.logic.chartGenerators; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Set; + +import de.deadlocker8.budgetmaster.logic.Helpers; +import de.deadlocker8.budgetmaster.logic.MonthInOutSum; +import javafx.event.EventHandler; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.chart.BarChart; +import javafx.scene.chart.CategoryAxis; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; + +@Deprecated +public class BarChartGenerator +{ + private ArrayList<MonthInOutSum> monthInOutSums; + private String currency; + + public BarChartGenerator(ArrayList<MonthInOutSum> monthInOutSums, String currency) + { + this.monthInOutSums = monthInOutSums; + this.currency = currency; + } + + public BarChart<String, Number> generate() + { + final CategoryAxis xAxis = new CategoryAxis(); + final NumberAxis yAxis = new NumberAxis(); + final BarChart<String, Number> generatedChart = new BarChart<>(xAxis, yAxis); + generatedChart.setTitle(null); + + xAxis.setLabel(""); + yAxis.setLabel("Summe in " + currency); + + XYChart.Series<String, Number> seriesIN = new XYChart.Series<String, Number>(); + seriesIN.setName("Einnahmen"); + XYChart.Series<String, Number> seriesOUT = new XYChart.Series<String, Number>(); + seriesOUT.setName("Ausgaben"); + + for(MonthInOutSum currentItem : monthInOutSums) + { + String label = currentItem.getDate().toString("MMMM YY"); + + seriesIN.getData().add(new XYChart.Data<String, Number>(label, currentItem.getBudgetIN()/100.0)); + seriesOUT.getData().add(new XYChart.Data<String, Number>(label, currentItem.getBudgetOUT()/100.0)); + } + + generatedChart.getData().add(seriesIN); + generatedChart.getData().add(seriesOUT); + + generatedChart.setLegendVisible(true); + + // add tooltip to every segment + generatedChart.getData().stream().forEach(tool -> { + for(XYChart.Data<String, Number> data : tool.getData()) + { + Tooltip tooltip = new Tooltip(); + + tooltip.setText(Helpers.NUMBER_FORMAT.format(data.getYValue()).replace(".", ",") + currency); + Tooltip.install(tool.getNode(), tooltip); + Node node = data.getNode(); + node.setOnMouseEntered(new EventHandler<MouseEvent>() + { + @Override + public void handle(MouseEvent event) + { + Point2D p = node.localToScreen(event.getX() + 5, event.getY() + 7); + tooltip.show(node, p.getX(), p.getY()); + } + }); + node.setOnMouseExited(new EventHandler<MouseEvent>() + { + + @Override + public void handle(MouseEvent event) + { + tooltip.hide(); + } + }); + } + }); + + // style bar for incomes + for(Node n : generatedChart.lookupAll(".default-color0.chart-bar")) + { + n.setStyle("-fx-bar-fill: " + Helpers.COLOR_INCOME + ";"); + } + + // style bar for payments + for(Node n : generatedChart.lookupAll(".default-color1.chart-bar")) + { + n.setStyle("-fx-bar-fill: " + Helpers.COLOR_PAYMENT + ";"); + } + + //style legend item according to color + Set<Node> nodes = generatedChart.lookupAll(".chart-legend-item"); + if(nodes.size() > 0) + { + Iterator<Node> iterator = nodes.iterator(); + int counter = 0; + while(iterator.hasNext()) + { + Node node = iterator.next(); + if(node instanceof Label) + { + Label labelLegendItem = (Label)node; + if(counter == 0) + { + labelLegendItem.getGraphic().setStyle("-fx-background-color: " + Helpers.COLOR_INCOME + ";"); + } + else + { + labelLegendItem.getGraphic().setStyle("-fx-background-color: " + Helpers.COLOR_PAYMENT + ";"); + } + } + counter++; + } + } + + return generatedChart; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..70c380cdd257b9892694009c2f1d265eb76213a6 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/CategoriesChartGenerator.java @@ -0,0 +1,141 @@ +package de.deadlocker8.budgetmaster.logic.chartGenerators; + +import java.util.ArrayList; + +import de.deadlocker8.budgetmaster.logic.CategoryInOutSum; +import de.deadlocker8.budgetmaster.logic.Helpers; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import tools.ConvertTo; + +public class CategoriesChartGenerator +{ + private String title; + private ArrayList<CategoryInOutSum> categoryInOutSums; + private boolean useBudgetIN; + private String currency; + private double total; + + public CategoriesChartGenerator(String title, ArrayList<CategoryInOutSum> categoryInOutSums, boolean useBudgetIN, String currency) + { + this.title = title; + this.categoryInOutSums = categoryInOutSums; + this.useBudgetIN = useBudgetIN; + this.currency = currency; + this.total = getTotal(categoryInOutSums, useBudgetIN); + } + + public VBox generate() + { + VBox generatedChart = new VBox(); + HBox chart = new HBox(); + chart.setMinHeight(30); + + Label labelTitle = new Label(title); + labelTitle.setStyle("-fx-font-size: 16; -fx-font-weight: bold;"); + generatedChart.getChildren().add(labelTitle); + VBox.setMargin(labelTitle, new Insets(0, 0, 10, 0)); + + for(CategoryInOutSum currentItem : categoryInOutSums) + { + Label currentPart = new Label(); + currentPart.setStyle("-fx-background-color: " + ConvertTo.toRGBHexWithoutOpacity(currentItem.getColor())); + currentPart.prefHeightProperty().bind(chart.heightProperty()); + chart.getChildren().add(currentPart); + + double value; + if(useBudgetIN) + { + value = currentItem.getBudgetIN() / 100.0; + } + else + { + value = -currentItem.getBudgetOUT() / 100.0; + } + + double percentage = value / total; + + currentPart.prefWidthProperty().bind(chart.widthProperty().multiply(percentage)); + + Tooltip tooltip = new Tooltip(); + tooltip.setText(Helpers.NUMBER_FORMAT.format(percentage*100) + " %\n" + Helpers.NUMBER_FORMAT.format(value).replace(".", ",") + currency);// + currentPart.setTooltip(tooltip); + } + + generatedChart.getChildren().add(chart); + + return generatedChart; + } + + public GridPane generateLegend() + { + ArrayList<HBox> legendItems = new ArrayList<>(); + for(CategoryInOutSum currentItem : categoryInOutSums) + { + String label = currentItem.getName(); + if(label.equals("NONE")) + { + label = "Keine Kategorie"; + } + legendItems.add(getLegendItem(label, currentItem.getColor())); + } + + int legendWidth = (int)Math.ceil(Math.sqrt(legendItems.size())); + GridPane legend = new GridPane(); + legend.setPadding(new Insets(10)); + legend.setHgap(20); + legend.setVgap(10); + legend.setAlignment(Pos.CENTER); + legend.setStyle("-fx-background-color: #EEEEEE; -fx-border-color: #212121; -fx-border-width: 1; -fx-border-radius: 5;"); + + for(int i = 0; i < legendItems.size(); i++) + { + int columnIndex = i % legendWidth; + int rowIndex = i / 4; + legend.add(legendItems.get(i), columnIndex, rowIndex); + } + + return legend; + } + + private HBox getLegendItem(String name, Color color) + { + HBox legendItem = new HBox(); + Label labelCircle = new Label(); + labelCircle.setMinWidth(20); + labelCircle.setMinHeight(20); + labelCircle.setStyle("-fx-background-color: " + ConvertTo.toRGBHexWithoutOpacity(color) + "; -fx-background-radius: 50%; -fx-border-width: 1; -fx-border-color: black - fx-border-radius: 50%"); + + Label labelText = new Label(name); + labelText.setStyle("-fx-font-weight: bold;"); + + legendItem.getChildren().add(labelCircle); + legendItem.getChildren().add(labelText); + HBox.setMargin(labelText, new Insets(0, 0, 0, 5)); + + return legendItem; + } + + private double getTotal(ArrayList<CategoryInOutSum> categoryInOutSums, boolean useBudgetIN) + { + double total = 0; + for(CategoryInOutSum currentItem : categoryInOutSums) + { + if(useBudgetIN) + { + total += currentItem.getBudgetIN() / 100.0; + } + else + { + total += -currentItem.getBudgetOUT() / 100.0; + } + } + return total; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..8888a5bd140f83f962677215f344645f5326de5f --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/LineChartGenerator.java @@ -0,0 +1,139 @@ +package de.deadlocker8.budgetmaster.logic.chartGenerators; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Set; + +import de.deadlocker8.budgetmaster.logic.Helpers; +import de.deadlocker8.budgetmaster.logic.MonthInOutSum; +import javafx.event.EventHandler; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.chart.CategoryAxis; +import javafx.scene.chart.LineChart; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; + +public class LineChartGenerator +{ + private ArrayList<MonthInOutSum> monthInOutSums; + private String currency; + + public LineChartGenerator(ArrayList<MonthInOutSum> monthInOutSums, String currency) + { + this.monthInOutSums = monthInOutSums; + this.currency = currency; + } + + public LineChart<String, Number> generate() + { + final CategoryAxis xAxis = new CategoryAxis(); + final NumberAxis yAxis = new NumberAxis(); + final LineChart<String, Number> generatedChart = new LineChart<>(xAxis, yAxis); + generatedChart.setTitle(null); + + xAxis.setLabel(""); + yAxis.setLabel("Summe in " + currency); + + XYChart.Series<String, Number> seriesIN = new XYChart.Series<String, Number>(); + seriesIN.setName("Einnahmen"); + XYChart.Series<String, Number> seriesOUT = new XYChart.Series<String, Number>(); + seriesOUT.setName("Ausgaben"); + + for(MonthInOutSum currentItem : monthInOutSums) + { + String label = currentItem.getDate().toString("MMMM YY"); + + seriesIN.getData().add(new XYChart.Data<String, Number>(label, currentItem.getBudgetIN() / 100.0)); + seriesOUT.getData().add(new XYChart.Data<String, Number>(label, -currentItem.getBudgetOUT() / 100.0)); + } + + generatedChart.getData().add(seriesIN); + generatedChart.getData().add(seriesOUT); + + generatedChart.setLegendVisible(true); + + // add tooltip to every segment + generatedChart.getData().stream().forEach(tool -> { + for(XYChart.Data<String, Number> data : tool.getData()) + { + Tooltip tooltip = new Tooltip(); + + tooltip.setText(Helpers.NUMBER_FORMAT.format(data.getYValue()).replace(".", ",") + currency); + Tooltip.install(tool.getNode(), tooltip); + Node node = data.getNode(); + node.setOnMouseEntered(new EventHandler<MouseEvent>() + { + @Override + public void handle(MouseEvent event) + { + Point2D p = node.localToScreen(event.getX() + 5, event.getY() + 7); + tooltip.show(node, p.getX(), p.getY()); + } + }); + node.setOnMouseExited(new EventHandler<MouseEvent>() + { + @Override + public void handle(MouseEvent event) + { + tooltip.hide(); + } + }); + } + }); + + // style line for incomes + for(Node n : generatedChart.lookupAll(".default-color0.chart-series-line")) + { + n.setStyle("-fx-stroke: " + Helpers.COLOR_INCOME + ";"); + } + + // style line dots for incomes + for(Node n : generatedChart.lookupAll(".default-color0.chart-line-symbol")) + { + n.setStyle("-fx-background-color: " + Helpers.COLOR_INCOME + ", white;"); + } + + // style line for payments + for(Node n : generatedChart.lookupAll(".default-color1.chart-series-line")) + { + n.setStyle("-fx-stroke: " + Helpers.COLOR_PAYMENT + ";"); + } + + // style line dots for payments + for(Node n : generatedChart.lookupAll(".default-color1.chart-line-symbol")) + { + n.setStyle("-fx-background-color: " + Helpers.COLOR_PAYMENT + ", white;"); + } + + // style legend item according to color + Set<Node> nodes = generatedChart.lookupAll(".chart-legend-item"); + if(nodes.size() > 0) + { + Iterator<Node> iterator = nodes.iterator(); + int counter = 0; + while(iterator.hasNext()) + { + Node node = iterator.next(); + if(node instanceof Label) + { + Label labelLegendItem = (Label)node; + if(counter == 0) + { + labelLegendItem.getGraphic().setStyle("-fx-background-color: " + Helpers.COLOR_INCOME + ";"); + } + else + { + labelLegendItem.getGraphic().setStyle("-fx-background-color: " + Helpers.COLOR_PAYMENT + ";"); + } + } + counter++; + } + } + + return generatedChart; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..3b5140da494b0ad1fae519f54e6452b4a70ae759 --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/MonthChartGenerator.java @@ -0,0 +1,199 @@ +package de.deadlocker8.budgetmaster.logic.chartGenerators; + +import java.util.ArrayList; + +import de.deadlocker8.budgetmaster.logic.CategoryInOutSum; +import de.deadlocker8.budgetmaster.logic.Helpers; +import de.deadlocker8.budgetmaster.logic.MonthInOutSum; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.Separator; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.TextAlignment; +import tools.ConvertTo; + +public class MonthChartGenerator +{ + private ArrayList<MonthInOutSum> monthInOutSums; + private String currency; + + public MonthChartGenerator(ArrayList<MonthInOutSum> monthInOutSums, String currency) + { + this.monthInOutSums = monthInOutSums; + this.currency = currency; + } + + public HBox generate() + { + HBox generatedChart = new HBox(); + generatedChart.setAlignment(Pos.TOP_CENTER); + generatedChart.setSpacing(25); + + double total = getMaximum(monthInOutSums); + + for(MonthInOutSum currentMonthSum : monthInOutSums) + { + VBox chartPart = new VBox(); + chartPart.setAlignment(Pos.TOP_CENTER); + + HBox hboxChart = new HBox(); + hboxChart.setAlignment(Pos.BOTTOM_CENTER); + hboxChart.setSpacing(10); + VBox chartIncome = generateChart(currentMonthSum.getSums(), total, true); + hboxChart.getChildren().add(chartIncome); + HBox.setHgrow(chartIncome, Priority.ALWAYS); + VBox chartPayment = generateChart(currentMonthSum.getSums(), total, false); + hboxChart.getChildren().add(chartPayment); + HBox.setHgrow(chartPayment, Priority.ALWAYS); + + chartPart.getChildren().add(hboxChart); + VBox.setVgrow(hboxChart, Priority.ALWAYS); + + Label labelTitle = new Label(currentMonthSum.getDate().toString("MMMM \nYY")); + labelTitle.setStyle("-fx-font-size: 12;"); + labelTitle.setTextAlignment(TextAlignment.CENTER); + chartPart.getChildren().add(labelTitle); + VBox.setMargin(labelTitle, new Insets(10, 0, 0, 0)); + + generatedChart.getChildren().add(chartPart); + generatedChart.getChildren().add(new Separator(Orientation.VERTICAL)); + } + + return generatedChart; + } + + private VBox generateChart(ArrayList<CategoryInOutSum> categoryInOutSums, double total, boolean useBudgetIN) + { + VBox result = new VBox(); + Label labelAmount = new Label(Helpers.NUMBER_FORMAT.format(getTotal(categoryInOutSums, useBudgetIN)).replace(".", ",") + currency); + labelAmount.setStyle("-fx-font-size: 12; -fx-font-weight: bold;"); + result.getChildren().add(labelAmount); + VBox.setMargin(labelAmount, new Insets(0, 0, 10, 0)); + + VBox chart = new VBox(); + chart.setAlignment(Pos.BOTTOM_CENTER); + + for(CategoryInOutSum currentItem : categoryInOutSums) + { + Label currentPart = new Label(); + currentPart.setStyle("-fx-background-color: " + ConvertTo.toRGBHexWithoutOpacity(currentItem.getColor())); + currentPart.prefWidthProperty().bind(chart.widthProperty()); + chart.getChildren().add(currentPart); + + double value; + if(useBudgetIN) + { + value = currentItem.getBudgetIN() / 100.0; + } + else + { + value = -currentItem.getBudgetOUT() / 100.0; + } + + double percentage = value / total; + + currentPart.setMinHeight(0); + currentPart.prefHeightProperty().bind(chart.heightProperty().multiply(percentage)); + + Tooltip tooltip = new Tooltip(); + tooltip.setText(currentItem.getName() + "\n"+ Helpers.NUMBER_FORMAT.format(percentage * 100) + " %\n" + Helpers.NUMBER_FORMAT.format(value).replace(".", ",") + currency);// + currentPart.setTooltip(tooltip); + } + + result.getChildren().add(chart); + VBox.setVgrow(chart, Priority.ALWAYS); + + return result; + } + + public GridPane generateLegend() + { + ArrayList<HBox> legendItems = new ArrayList<>(); + for(CategoryInOutSum currentItem : monthInOutSums.get(0).getSums()) + { + String label = currentItem.getName(); + if(label.equals("NONE")) + { + label = "Keine Kategorie"; + } + legendItems.add(getLegendItem(label, currentItem.getColor())); + } + + int legendWidth = (int)Math.ceil(Math.sqrt(legendItems.size())); + GridPane legend = new GridPane(); + legend.setPadding(new Insets(10)); + legend.setHgap(20); + legend.setVgap(10); + legend.setAlignment(Pos.CENTER); + legend.setStyle("-fx-background-color: #EEEEEE; -fx-border-color: #212121; -fx-border-width: 1; -fx-border-radius: 5;"); + + for(int i = 0; i < legendItems.size(); i++) + { + int columnIndex = i % legendWidth; + int rowIndex = i / 4; + legend.add(legendItems.get(i), columnIndex, rowIndex); + } + + return legend; + } + + private HBox getLegendItem(String name, Color color) + { + HBox legendItem = new HBox(); + Label labelCircle = new Label(); + labelCircle.setMinWidth(20); + labelCircle.setMinHeight(20); + labelCircle.setStyle("-fx-background-color: " + ConvertTo.toRGBHexWithoutOpacity(color) + "; -fx-background-radius: 50%; -fx-border-width: 1; -fx-border-color: black - fx-border-radius: 50%"); + + Label labelText = new Label(name); + labelText.setStyle("-fx-font-weight: bold;"); + + legendItem.getChildren().add(labelCircle); + legendItem.getChildren().add(labelText); + HBox.setMargin(labelText, new Insets(0, 0, 0, 5)); + + return legendItem; + } + + private double getTotal(ArrayList<CategoryInOutSum> categoryInOutSums, boolean useBudgetIN) + { + double total = 0; + for(CategoryInOutSum currentItem : categoryInOutSums) + { + if(useBudgetIN) + { + total += currentItem.getBudgetIN() / 100.0; + } + else + { + total += -currentItem.getBudgetOUT() / 100.0; + } + } + return total; + } + + private double getMaximum(ArrayList<MonthInOutSum> monthInOutSums) + { + double maximum = 0; + for(MonthInOutSum currentItem : monthInOutSums) + { + if(currentItem.getBudgetIN() > maximum) + { + maximum = currentItem.getBudgetIN(); + } + + if(Math.abs(currentItem.getBudgetOUT()) > maximum) + { + maximum = Math.abs(currentItem.getBudgetOUT()); + } + } + return maximum / 100.0; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..382d0a77dfede19bc254cfa0f3f40387c2af5c5f --- /dev/null +++ b/src/de/deadlocker8/budgetmaster/logic/chartGenerators/PieChartGenerator.java @@ -0,0 +1,125 @@ +package de.deadlocker8.budgetmaster.logic.chartGenerators; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Set; + +import de.deadlocker8.budgetmaster.logic.CategoryInOutSum; +import de.deadlocker8.budgetmaster.logic.Helpers; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.chart.PieChart; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import tools.ConvertTo; + +@Deprecated +public class PieChartGenerator +{ + private String title; + private ArrayList<CategoryInOutSum> categoryInOutSums; + private boolean useBudgetIN; + private String currency; + + public PieChartGenerator(String title, ArrayList<CategoryInOutSum> categoryInOutSums, boolean useBudgetIN, String currency) + { + this.title = title; + this.categoryInOutSums = categoryInOutSums; + this.useBudgetIN = useBudgetIN; + this.currency = currency; + } + + public PieChart generate() + { + ArrayList<PieChart.Data> data = new ArrayList<>(); + + for(CategoryInOutSum currentItem : categoryInOutSums) + { + String label = currentItem.getName(); + if(label.equals("NONE")) + { + label = "Keine Kategorie"; + } + + if(useBudgetIN) + { + data.add(new PieChart.Data(label, currentItem.getBudgetIN()/100.0)); + } + else + { + data.add(new PieChart.Data(label, -currentItem.getBudgetOUT()/100.0)); + } + } + + ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList(data); + + final PieChart chart = new PieChart(pieChartData); + chart.setTitle(title); + + //add tooltip to every segment that shows percentage as double value + chart.getData().stream().forEach(tool -> + { + Tooltip tooltip = new Tooltip(); + + double total = 0; + for(int i = 0; i < chart.getData().size(); i++) + { + PieChart.Data currentData = chart.getData().get(i); + total += currentData.getPieValue(); + String currentColor = ConvertTo.toRGBHexWithoutOpacity(categoryInOutSums.get(i).getColor()); + currentData.getNode().setStyle("-fx-pie-color: " + currentColor + ";"); + } + + //style legend item according to color + Set<Node> nodes = chart.lookupAll(".chart-legend-item"); + if(nodes.size() > 0) + { + Iterator<Node> iterator = nodes.iterator(); + int counter = 0; + while(iterator.hasNext()) + { + Node node = iterator.next(); + if(node instanceof Label) + { + Label labelLegendItem = (Label)node; + labelLegendItem.getGraphic().setStyle("-fx-background-color: " + ConvertTo.toRGBHexWithoutOpacity(categoryInOutSums.get(counter).getColor()) + ";"); + } + counter++; + } + } + + double pieValue = tool.getPieValue(); + double percentage = (pieValue / total) * 100; + String percent = String.valueOf(percentage); + percent = percent.substring(0, percent.indexOf(".") + 2); + + tooltip.setText(percent + " %\n" + Helpers.NUMBER_FORMAT.format(pieValue).replace(".", ",") + currency); + Tooltip.install(tool.getNode(), tooltip); + Node node = tool.getNode(); + node.setOnMouseEntered(new EventHandler<MouseEvent>() + { + @Override + public void handle(MouseEvent event) + { + Point2D p = node.localToScreen(event.getX() + 5, event.getY() + 7); + tooltip.show(node, p.getX(), p.getY()); + } + }); + node.setOnMouseExited(new EventHandler<MouseEvent>() + { + + @Override + public void handle(MouseEvent event) + { + tooltip.hide(); + } + }); + }); + + return chart; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/main/Main.java b/src/de/deadlocker8/budgetmaster/main/Main.java index ebbc0ab0f3027ef5a9bd76337a1124135d6b7736..a2a24cf7e5355bfad397ae5afdc5b0ef628d1023 100644 --- a/src/de/deadlocker8/budgetmaster/main/Main.java +++ b/src/de/deadlocker8/budgetmaster/main/Main.java @@ -27,7 +27,7 @@ public class Main extends Application FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("de/deadlocker8/budgetmaster/ui/GUI.fxml")); Parent root = (Parent)loader.load(); - Scene scene = new Scene(root, 610, 650); + Scene scene = new Scene(root, 650, 650); ((Controller)loader.getController()).init(stage); diff --git a/src/de/deadlocker8/budgetmaster/ui/ChartController.java b/src/de/deadlocker8/budgetmaster/ui/ChartController.java index 30931aa6ffecb297d4b9702206f16b8a18de25e3..b4ceb4f63bcf6469f8a5692b83a906b0b26ab55c 100644 --- a/src/de/deadlocker8/budgetmaster/ui/ChartController.java +++ b/src/de/deadlocker8/budgetmaster/ui/ChartController.java @@ -1,11 +1,61 @@ package de.deadlocker8.budgetmaster.ui; +import java.time.LocalDate; +import java.util.ArrayList; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; + +import de.deadlocker8.budgetmaster.logic.CategoryInOutSum; +import de.deadlocker8.budgetmaster.logic.Helpers; +import de.deadlocker8.budgetmaster.logic.MonthInOutSum; +import de.deadlocker8.budgetmaster.logic.ServerConnection; +import de.deadlocker8.budgetmaster.logic.chartGenerators.CategoriesChartGenerator; +import de.deadlocker8.budgetmaster.logic.chartGenerators.LineChartGenerator; +import de.deadlocker8.budgetmaster.logic.chartGenerators.MonthChartGenerator; +import fontAwesome.FontIcon; +import fontAwesome.FontIconType; +import javafx.collections.FXCollections; import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.chart.LineChart; +import javafx.scene.control.Accordion; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.DateCell; +import javafx.scene.control.DatePicker; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ScrollPane.ScrollBarPolicy; +import javafx.scene.control.ToggleGroup; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.util.Callback; +import logger.Logger; +import tools.AlertGenerator; public class ChartController implements Refreshable { @FXML private AnchorPane anchorPaneMain; + @FXML private Accordion accordion; + @FXML private DatePicker datePickerStart; + @FXML private VBox vboxChartCategories; + @FXML private DatePicker datePickerEnd; + @FXML private VBox vboxChartMonth; + @FXML private Button buttonChartCategoriesShow; + @FXML private ComboBox<String> comboBoxStartMonth; + @FXML private ComboBox<String> comboBoxStartYear; + @FXML private ComboBox<String> comboBoxEndMonth; + @FXML private ComboBox<String> comboBoxEndYear; + @FXML private Button buttonChartMonthShow; + @FXML private RadioButton radioButtonBars; + @FXML private RadioButton radioButtonLines; private Controller controller; @@ -14,12 +64,167 @@ public class ChartController implements Refreshable this.controller = controller; anchorPaneMain.setStyle("-fx-background-color: #F4F4F4;"); + vboxChartCategories.setStyle("-fx-background-color: #F4F4F4;"); + vboxChartCategories.setSpacing(20); + vboxChartMonth.setStyle("-fx-background-color: #F4F4F4;"); + FontIcon iconShow = new FontIcon(FontIconType.CHECK); + iconShow.setSize(16); + iconShow.setColor(Color.WHITE); + buttonChartCategoriesShow.setStyle("-fx-background-color: #2E79B9;"); + buttonChartCategoriesShow.setGraphic(iconShow); + + FontIcon iconShow2 = new FontIcon(FontIconType.CHECK); + iconShow2.setSize(16); + iconShow2.setColor(Color.WHITE); + buttonChartMonthShow.setStyle("-fx-background-color: #2E79B9;"); + buttonChartMonthShow.setGraphic(iconShow2); + + datePickerEnd.setDayCellFactory(new Callback<DatePicker, DateCell>() + { + @Override + public DateCell call(DatePicker param) + { + return new DateCell() + { + @Override + public void updateItem(LocalDate item, boolean empty) + { + super.updateItem(item, empty); + if(item.isBefore(datePickerStart.getValue().plusDays(1))) + { + setDisable(true); + setStyle("-fx-background-color: #ffc0cb;"); + } + } + }; + } + }); + + comboBoxStartMonth.setItems(FXCollections.observableArrayList(Helpers.getMonthList())); + comboBoxStartMonth.setValue(controller.getCurrentDate().minusMonths(5).toString("MMMM")); + + comboBoxStartYear.setItems(FXCollections.observableArrayList(Helpers.getYearList())); + comboBoxStartYear.setValue(String.valueOf(controller.getCurrentDate().minusMonths(5).getYear())); + + comboBoxEndMonth.setItems(FXCollections.observableArrayList(Helpers.getMonthList())); + comboBoxEndMonth.setValue(controller.getCurrentDate().plusMonths(6).toString("MMMM")); + + comboBoxEndYear.setItems(FXCollections.observableArrayList(Helpers.getYearList())); + comboBoxEndYear.setValue(String.valueOf(controller.getCurrentDate().plusMonths(6).getYear())); + + final ToggleGroup toggleGroup = new ToggleGroup(); + radioButtonBars.setToggleGroup(toggleGroup); + radioButtonBars.setSelected(true); + radioButtonLines.setToggleGroup(toggleGroup); + + accordion.setExpandedPane(accordion.getPanes().get(0)); + vboxChartMonth.setSpacing(15); + } + + public void chartCategoriesShow() + { + DateTime startDate = DateTime.parse(datePickerStart.getValue().toString()); + DateTime endDate = DateTime.parse(datePickerEnd.getValue().toString()); + + try + { + ServerConnection connection = new ServerConnection(controller.getSettings()); + ArrayList<CategoryInOutSum> sums = connection.getCategoryInOutSumForMonth(startDate, endDate); + + vboxChartCategories.getChildren().clear(); + + CategoriesChartGenerator generator = new CategoriesChartGenerator("Einnahmen nach Kategorien", sums, true, controller.getSettings().getCurrency()); + vboxChartCategories.getChildren().add(generator.generate()); + generator = new CategoriesChartGenerator("Ausgaben nach Kategorien", sums, false, controller.getSettings().getCurrency()); + vboxChartCategories.getChildren().add(generator.generate()); + + Region spacer = new Region(); + vboxChartCategories.getChildren().add(spacer); + VBox.setVgrow(spacer, Priority.ALWAYS); + + vboxChartCategories.getChildren().add(generator.generateLegend()); + } + catch(Exception e) + { + Logger.error(e); + // TODO + // controller.showConnectionErrorAlert(e.getMessage()); + } + } + + public void chartMonthShow() + { + vboxChartMonth.getChildren().clear(); + + String startMonth = comboBoxStartMonth.getValue(); + String startYear = comboBoxStartYear.getValue(); + String endMonth = comboBoxEndMonth.getValue(); + String endYear = comboBoxEndYear.getValue(); + + String startDateString = "01-" + startMonth + "-" + startYear; + DateTime startDate = DateTime.parse(startDateString, DateTimeFormat.forPattern("dd-MMMM-YYYY")); + + String endDateString = "01-" + endMonth + "-" + endYear; + DateTime endDate = DateTime.parse(endDateString, DateTimeFormat.forPattern("dd-MMMM-YYYY")); + + if(endDate.isBefore(startDate)) + { + AlertGenerator.showAlert(AlertType.WARNING, "Warnung", "", "Das Enddatum darf nicht vor dem Startdatum liegen.", controller.getIcon(), controller.getStage(), null, false); + return; + } + + try + { + ServerConnection connection = new ServerConnection(controller.getSettings()); + ArrayList<MonthInOutSum> sums = connection.getMonthInOutSum(startDate, endDate); + + vboxChartMonth.getChildren().clear(); + + if(radioButtonBars.isSelected()) + { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER); + scrollPane.setFocusTraversable(false); + scrollPane.setStyle("-fx-background-color: transparent; -fx-background-insets: 0; -fx-border-color: transparent; -fx-border-width: 0; -fx-border-insets: 0;"); + scrollPane.setPadding(new Insets(0, 0, 10, 0)); + + MonthChartGenerator generator = new MonthChartGenerator(sums, controller.getSettings().getCurrency()); + HBox generatedChart = generator.generate(); + scrollPane.setContent(generatedChart); + generatedChart.prefHeightProperty().bind(scrollPane.heightProperty().subtract(30)); + vboxChartMonth.getChildren().add(scrollPane); + VBox.setVgrow(scrollPane, Priority.ALWAYS); + vboxChartMonth.getChildren().add(generator.generateLegend()); + } + else + { + LineChartGenerator generator = new LineChartGenerator(sums, controller.getSettings().getCurrency()); + LineChart<String, Number> chartMonth = generator.generate(); + vboxChartMonth.getChildren().add(chartMonth); + VBox.setVgrow(chartMonth, Priority.ALWAYS); + } + } + catch(Exception e) + { + Logger.error(e); + // TODO + // controller.showConnectionErrorAlert(e.getMessage()); + } } @Override public void refresh() { - //TODO Auto-generated method stub - + // chart categories + LocalDate startDate = LocalDate.parse(controller.getCurrentDate().withDayOfMonth(1).toString("yyyy-MM-dd")); + LocalDate endDate = LocalDate.parse(controller.getCurrentDate().dayOfMonth().withMaximumValue().toString("yyy-MM-dd")); + + datePickerStart.setValue(startDate); + datePickerEnd.setValue(endDate); + + chartCategoriesShow(); + + // chart month + chartMonthShow(); } } \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml b/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml index e4461f90a25c86b4af4d245e8fd3e8d015bb083f..7ffa60128c56a3f29617e506560b0eef6f6963ad 100644 --- a/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml +++ b/src/de/deadlocker8/budgetmaster/ui/ChartTab.fxml @@ -1,10 +1,141 @@ <?xml version="1.0" encoding="UTF-8"?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Accordion?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ComboBox?> +<?import javafx.scene.control.DatePicker?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.RadioButton?> +<?import javafx.scene.control.TitledPane?> <?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> <AnchorPane fx:id="anchorPaneMain" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.deadlocker8.budgetmaster.ui.ChartController"> <children> - <VBox alignment="TOP_CENTER" layoutY="24.0" prefHeight="562.0" prefWidth="772.0" spacing="25.0" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="25.0" /> + <Accordion fx:id="accordion" AnchorPane.bottomAnchor="14.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="25.0"> + <panes> + <TitledPane animated="false" text="Einnahmen/Ausgaben nach Kategorien"> + <content> + <VBox spacing="20.0"> + <children> + <HBox alignment="CENTER" prefHeight="8.0" prefWidth="750.0"> + <children> + <HBox alignment="CENTER_RIGHT" spacing="10.0" HBox.hgrow="ALWAYS"> + <children> + <Label text="Von:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <DatePicker fx:id="datePickerStart" /> + </children> + <HBox.margin> + <Insets right="15.0" /> + </HBox.margin> + </HBox> + <HBox alignment="CENTER_LEFT" spacing="10.0" HBox.hgrow="ALWAYS"> + <children> + <Label text="Bis:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <DatePicker fx:id="datePickerEnd" /> + <Button fx:id="buttonChartCategoriesShow" mnemonicParsing="false" onAction="#chartCategoriesShow"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + <HBox.margin> + <Insets left="15.0" /> + </HBox.margin> + </Button> + </children> + <HBox.margin> + <Insets left="15.0" /> + </HBox.margin> + </HBox> + </children> + </HBox> + <VBox fx:id="vboxChartCategories" VBox.vgrow="ALWAYS"> + <VBox.margin> + <Insets /> + </VBox.margin> + <padding> + <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" /> + </padding></VBox> + </children> + </VBox> + </content> + <font> + <Font name="System Bold" size="12.0" /> + </font> + </TitledPane> + <TitledPane animated="false" text="Einnahmen/Ausgaben pro Monat"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + <content> + <VBox spacing="20.0"> + <children> + <HBox alignment="CENTER_LEFT" prefHeight="8.0" prefWidth="750.0" spacing="50.0"> + <children> + <VBox spacing="15.0"> + <children> + <HBox alignment="CENTER_RIGHT" spacing="10.0"> + <children> + <Label prefHeight="25.0" prefWidth="45.0" text="Von:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <ComboBox fx:id="comboBoxStartMonth" prefHeight="25.0" prefWidth="110.0" /> + <ComboBox fx:id="comboBoxStartYear" prefWidth="70.0" /> + </children> + </HBox> + <HBox alignment="CENTER_LEFT" spacing="10.0"> + <children> + <Label prefWidth="45.0" text="Bis:"> + <font> + <Font name="System Bold" size="16.0" /> + </font> + </Label> + <ComboBox fx:id="comboBoxEndMonth" prefWidth="110.0" /> + <ComboBox fx:id="comboBoxEndYear" prefWidth="70.0" /> + </children> + </HBox> + </children> + </VBox> + <HBox alignment="CENTER_LEFT" spacing="15.0" HBox.hgrow="ALWAYS"> + <children> + <RadioButton fx:id="radioButtonBars" mnemonicParsing="false" text="Balken"> + <font> + <Font size="14.0" /> + </font></RadioButton> + <RadioButton fx:id="radioButtonLines" mnemonicParsing="false" text="Linien"> + <font> + <Font size="14.0" /> + </font></RadioButton> + <Button fx:id="buttonChartMonthShow" mnemonicParsing="false" onAction="#chartMonthShow"> + <font> + <Font name="System Bold" size="12.0" /> + </font> + <HBox.margin> + <Insets left="10.0" /> + </HBox.margin> + </Button> + </children> + </HBox> + </children> + </HBox> + <VBox fx:id="vboxChartMonth" prefHeight="200.0" prefWidth="100.0" VBox.vgrow="ALWAYS" /> + </children> + </VBox> + </content> + </TitledPane> + </panes> + </Accordion> </children> </AnchorPane> diff --git a/src/de/deadlocker8/budgetmaster/ui/Controller.java b/src/de/deadlocker8/budgetmaster/ui/Controller.java index 98ce6f6c0ee2b6354c500a48b8bf67c06913a498..1888e00c172aa44f43048e5eed571a1b914f8647 100644 --- a/src/de/deadlocker8/budgetmaster/ui/Controller.java +++ b/src/de/deadlocker8/budgetmaster/ui/Controller.java @@ -105,9 +105,7 @@ public class Controller Parent nodeTabChart = (Parent)fxmlLoader.load(); chartController = fxmlLoader.getController(); chartController.init(this); - tabCharts.setContent(nodeTabChart); - //TODO - tabCharts.setDisable(true); + tabCharts.setContent(nodeTabChart); fxmlLoader = new FXMLLoader(getClass().getResource("/de/deadlocker8/budgetmaster/ui/SettingsTab.fxml")); Parent nodeTabSettings = (Parent)fxmlLoader.load(); diff --git a/src/de/deadlocker8/budgetmaster/ui/NewPaymentController.java b/src/de/deadlocker8/budgetmaster/ui/NewPaymentController.java index 99218826d73daffb468e25cf98cccbc6020e348d..1cdd2f05dc59b1c6400127ab386abcfa60e34f2b 100644 --- a/src/de/deadlocker8/budgetmaster/ui/NewPaymentController.java +++ b/src/de/deadlocker8/budgetmaster/ui/NewPaymentController.java @@ -18,7 +18,6 @@ import de.deadlocker8.budgetmaster.ui.cells.RepeatingDayCell; import de.deadlocker8.budgetmaster.ui.cells.SmallCategoryCell; import fontAwesome.FontIcon; import fontAwesome.FontIconType; -import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Button; @@ -215,9 +214,18 @@ public class NewPaymentController toggleRepeatingArea(false); //preselect correct month and year - DateTime currentDate = controller.getCurrentDate(); - datePicker.setValue(LocalDate.now().withYear(currentDate.getYear()).withMonth(currentDate.getMonthOfYear()).withDayOfMonth(currentDate.getDayOfMonth())); - Platform.runLater(()->{datePicker.getEditor().clear();}); + DateTime currentDate = controller.getCurrentDate(); + if(DateTime.now().getDayOfMonth() > currentDate.dayOfMonth().withMaximumValue().getDayOfMonth()) + { + currentDate = currentDate.dayOfMonth().withMaximumValue(); + } + + LocalDate currentLocalDate = LocalDate.now().withYear(currentDate.getYear()) + .withMonth(currentDate.getMonthOfYear()) + .withDayOfMonth(currentDate.getDayOfMonth()); + datePicker.setValue(currentLocalDate); + datePicker.setEditable(false); + //Platform.runLater(()->{datePicker.getEditor().clear();}); } } diff --git a/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java b/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java index 0b18ba66d379d2a26901fe00f38a1c06fd6d1b74..e096c9a72a0c14fdabd8972136b64e235e739ac2 100644 --- a/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java +++ b/src/de/deadlocker8/budgetmaster/ui/cells/PaymentCell.java @@ -151,17 +151,17 @@ public class PaymentCell extends ListCell<Payment> Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle("Zahlung löschen"); alert.setHeaderText(""); - alert.setContentText("Diese Zahlung wirklich unwiederruflich löschen?"); + alert.setContentText("Diese Zahlung wirklich unwiderruflich löschen?"); Stage dialogStage = (Stage)alert.getDialogPane().getScene().getWindow(); dialogStage.getIcons().add(paymentController.getController().getIcon()); dialogStage.centerOnScreen(); if(item instanceof RepeatingPaymentEntry) { - alert.setContentText("Es handelt sich um eine wiederkehrende Zahlung. Wie soll gelöscht werden?"); + alert.setContentText("Es handelt sich um eine wiederkehrende Zahlung. Welche Zahlungen sollen gelöscht werden?"); - ButtonType buttonTypeOne = new ButtonType("Komplett löschen"); - ButtonType buttonTypeTwo = new ButtonType("Alle zukünftigen Löschen"); + ButtonType buttonTypeOne = new ButtonType("Alle"); + ButtonType buttonTypeTwo = new ButtonType("Alle zukünftigen"); ButtonType buttonTypeCancel = new ButtonType("Abbrechen", ButtonData.CANCEL_CLOSE); alert.getButtonTypes().setAll(buttonTypeOne, buttonTypeTwo, buttonTypeCancel); diff --git a/src/de/deadlocker8/budgetmaster/ui/colorPick/ColorPickGUI.fxml b/src/de/deadlocker8/budgetmaster/ui/colorPick/ColorPickGUI.fxml index c96c6768ced8598de0f3758b2a23d8c5133879c3..7b0f539542397085a05f4febd10d01875ca5c498 100644 --- a/src/de/deadlocker8/budgetmaster/ui/colorPick/ColorPickGUI.fxml +++ b/src/de/deadlocker8/budgetmaster/ui/colorPick/ColorPickGUI.fxml @@ -90,12 +90,12 @@ </HBox> <HBox alignment="BOTTOM_CENTER" prefHeight="0.0" prefWidth="299.0" spacing="25.0" VBox.vgrow="ALWAYS"> <children> - <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#save" text="Speichern"> + <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> <font> <Font name="System Bold" size="14.0" /> </font> </Button> - <Button fx:id="buttonCancel" mnemonicParsing="false" onAction="#cancel" text="Abbrechen"> + <Button fx:id="buttonSave" mnemonicParsing="false" onAction="#save" text="Speichern"> <font> <Font name="System Bold" size="14.0" /> </font> diff --git a/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java b/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java index e69feaf455d1d353fb0faa5724b47978217ab611..ebb47565aedf5c1f614acd25ec7d9abe90097951 100644 --- a/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java +++ b/src/de/deadlocker8/budgetmasterserver/main/DatabaseHandler.java @@ -356,6 +356,50 @@ public class DatabaseHandler return results; } + + public ArrayList<NormalPayment> getPaymentsBetween(String startDate, String endDate) + { + Statement stmt = null; + String query = "SELECT * FROM payment WHERE DATE(Date) BETWEEN '" + startDate + "' AND '" + endDate + "';"; + + ArrayList<NormalPayment> results = new ArrayList<>(); + try + { + stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(query); + + while(rs.next()) + { + int resultID = rs.getInt("ID"); + String name = rs.getString("Name"); + int amount = rs.getInt("amount"); + String date = rs.getString("Date"); + int categoryID = rs.getInt("CategoryID"); + String description = rs.getString("Description"); + + results.add(new NormalPayment(resultID, amount, date, categoryID, name, description)); + } + } + catch(SQLException e) + { + Logger.error(e); + } + finally + { + if(stmt != null) + { + try + { + stmt.close(); + } + catch(SQLException e) + { + } + } + } + + return results; + } public ArrayList<RepeatingPaymentEntry> getRepeatingPayments(int year, int month) { @@ -405,6 +449,54 @@ public class DatabaseHandler return results; } + + public ArrayList<RepeatingPaymentEntry> getRepeatingPaymentsBetween(String startDate, String endDate) + { + Statement stmt = null; + String query = "SELECT repeating_entry.ID, repeating_entry.RepeatingPaymentID, repeating_entry.Date, repeating_payment.Name, repeating_payment.CategoryID, repeating_payment.Amount, repeating_payment.RepeatInterval, repeating_payment.RepeatEndDate, repeating_payment.RepeatMonthDay, repeating_payment.Description FROM repeating_entry, repeating_payment WHERE repeating_entry.RepeatingPaymentID = repeating_payment.ID AND DATE(repeating_entry.Date) BETWEEN '" + startDate + "' AND '" + endDate + "';"; + + ArrayList<RepeatingPaymentEntry> results = new ArrayList<>(); + try + { + stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(query); + + while(rs.next()) + { + int resultID = rs.getInt("ID"); + int repeatingPaymentID = rs.getInt("repeatingPaymentID"); + String name = rs.getString("Name"); + String description = rs.getString("Description"); + int amount = rs.getInt("amount"); + String date = rs.getString("Date"); + int categoryID = rs.getInt("CategoryID"); + int repeatInterval = rs.getInt("RepeatInterval"); + String repeatEndDate = rs.getString("RepeatEndDate"); + int repeatMonthDay = rs.getInt("RepeatMonthDay"); + + results.add(new RepeatingPaymentEntry(resultID, repeatingPaymentID, date, amount, categoryID, name, description,repeatInterval, repeatEndDate, repeatMonthDay)); + } + } + catch(SQLException e) + { + Logger.error(e); + } + finally + { + if(stmt != null) + { + try + { + stmt.close(); + } + catch(SQLException e) + { + } + } + } + + return results; + } public ArrayList<RepeatingPayment> getAllRepeatingPayments() { diff --git a/src/de/deadlocker8/budgetmasterserver/main/Main.java b/src/de/deadlocker8/budgetmasterserver/main/Main.java index 36d7f55ee9b962325f77541856c01c8a3222e6c4..f9c4fd0c4ca100f62da146f4993d63daf07927bc 100644 --- a/src/de/deadlocker8/budgetmasterserver/main/Main.java +++ b/src/de/deadlocker8/budgetmasterserver/main/Main.java @@ -13,6 +13,7 @@ import de.deadlocker8.budgetmasterserver.server.SparkServer; import logger.FileOutputMode; import logger.LogLevel; import logger.Logger; +import tools.PathUtils; public class Main { @@ -24,7 +25,7 @@ public class Main Logger.appInfo(bundle.getString("app.name"), bundle.getString("version.name"), bundle.getString("version.code"), bundle.getString("version.date")); try { - File logFolder = Paths.get(SparkServer.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().toFile(); + File logFolder = Paths.get(SparkServer.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().toFile(); Logger.enableFileOutput(logFolder, System.out, System.err, FileOutputMode.COMBINED); } catch(URISyntaxException e1) @@ -62,7 +63,6 @@ public class Main catch(URISyntaxException e1) { Logger.error(e1); - } - + } } -} +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java b/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java index 8c76dacaf72d84006eb05fe7556b966b61b7b2fd..506e12159c182251820fe1bd6d1b40629873f12d 100644 --- a/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java +++ b/src/de/deadlocker8/budgetmasterserver/server/SparkServer.java @@ -26,6 +26,8 @@ import de.deadlocker8.budgetmasterserver.server.category.CategoryGet; import de.deadlocker8.budgetmasterserver.server.category.CategoryGetAll; import de.deadlocker8.budgetmasterserver.server.category.CategoryUpdate; import de.deadlocker8.budgetmasterserver.server.categorybudget.CategoryBudgetGet; +import de.deadlocker8.budgetmasterserver.server.charts.CategoryInOutSumForMonth; +import de.deadlocker8.budgetmasterserver.server.charts.MonthInOutSum; import de.deadlocker8.budgetmasterserver.server.database.DatabaseDelete; import de.deadlocker8.budgetmasterserver.server.database.DatabaseExport; import de.deadlocker8.budgetmasterserver.server.database.DatabaseImport; @@ -114,8 +116,12 @@ public class SparkServer get("/categorybudget", new CategoryBudgetGet(handler, gson)); // Rest - get("/rest", new RestGet(handler, gson)); - + get("/rest", new RestGet(handler, gson)); + + //charts + get("/charts/categoryInOutSum", new CategoryInOutSumForMonth(handler, gson)); + get("/charts/monthInOutSum", new MonthInOutSum(handler, gson)); + // Database get("/database", new DatabaseExport(settings, gson)); post("/database", new DatabaseImport(handler, gson)); diff --git a/src/de/deadlocker8/budgetmasterserver/server/categorybudget/CategoryBudgetGet.java b/src/de/deadlocker8/budgetmasterserver/server/categorybudget/CategoryBudgetGet.java index 0b0859c69771c6405bf01921b09e09928f6ea535..367526afdbfb61f16c73bed92fd7f9fa5b49baa1 100644 --- a/src/de/deadlocker8/budgetmasterserver/server/categorybudget/CategoryBudgetGet.java +++ b/src/de/deadlocker8/budgetmasterserver/server/categorybudget/CategoryBudgetGet.java @@ -54,14 +54,7 @@ public class CategoryBudgetGet implements Route ArrayList<Payment> payments = new ArrayList<>(); payments.addAll(handler.getPayments(year, month)); payments.addAll(handler.getRepeatingPayments(year, month)); - Collections.sort(payments, new Comparator<Payment>() { - @Override - public int compare(Payment payment1, Payment payment2) - { - return payment2.getDate().compareTo(payment1.getDate()); - } - }); - + ArrayList<CategoryBudget> budgets = new ArrayList<>(); for(Category currentCategory : handler.getCategories()) diff --git a/src/de/deadlocker8/budgetmasterserver/server/charts/CategoryInOutSumForMonth.java b/src/de/deadlocker8/budgetmasterserver/server/charts/CategoryInOutSumForMonth.java new file mode 100644 index 0000000000000000000000000000000000000000..b1b2c6159f839b6e94c360eb2157a52ca22fb660 --- /dev/null +++ b/src/de/deadlocker8/budgetmasterserver/server/charts/CategoryInOutSumForMonth.java @@ -0,0 +1,74 @@ +package de.deadlocker8.budgetmasterserver.server.charts; + +import static spark.Spark.halt; + +import java.util.ArrayList; + +import com.google.gson.Gson; + +import de.deadlocker8.budgetmaster.logic.Category; +import de.deadlocker8.budgetmaster.logic.CategoryInOutSum; +import de.deadlocker8.budgetmaster.logic.Payment; +import de.deadlocker8.budgetmasterserver.main.DatabaseHandler; +import spark.Request; +import spark.Response; +import spark.Route; + +public class CategoryInOutSumForMonth implements Route +{ + private DatabaseHandler handler; + private Gson gson; + + public CategoryInOutSumForMonth(DatabaseHandler handler, Gson gson) + { + this.handler = handler; + this.gson = gson; + } + + @Override + public Object handle(Request req, Response res) throws Exception + { + if(!req.queryParams().contains("startDate") || !req.queryParams().contains("endDate")) + { + halt(400, "Bad Request"); + } + + try + { + ArrayList<Payment> payments = new ArrayList<>(); + payments.addAll(handler.getPaymentsBetween(req.queryMap("startDate").value(), req.queryMap("endDate").value())); + payments.addAll(handler.getRepeatingPaymentsBetween(req.queryMap("startDate").value(), req.queryMap("endDate").value())); + + ArrayList<CategoryInOutSum> inOutSums = new ArrayList<>(); + + for(Category currentCategory : handler.getCategories()) + { + inOutSums.add(new CategoryInOutSum(currentCategory.getID(), currentCategory.getName(), currentCategory.getColor(), 0, 0)); + CategoryInOutSum currentInOutSum = inOutSums.get(inOutSums.size() - 1); + for(Payment currentPayment : payments) + { + if(currentCategory.getID() == currentPayment.getCategoryID()) + { + int amount = currentPayment.getAmount(); + if(amount > 0) + { + currentInOutSum.setBudgetIN(currentInOutSum.getBudgetIN() + amount); + } + else + { + currentInOutSum.setBudgetOUT(currentInOutSum.getBudgetOUT() + amount); + } + } + } + } + + return gson.toJson(inOutSums); + } + catch(IllegalStateException ex) + { + halt(500, "Internal Server Error"); + } + + return null; + } +} \ No newline at end of file diff --git a/src/de/deadlocker8/budgetmasterserver/server/charts/MonthInOutSum.java b/src/de/deadlocker8/budgetmasterserver/server/charts/MonthInOutSum.java new file mode 100644 index 0000000000000000000000000000000000000000..cf770b2b9100989dc463e5f5e6a11a82cc2205e2 --- /dev/null +++ b/src/de/deadlocker8/budgetmasterserver/server/charts/MonthInOutSum.java @@ -0,0 +1,88 @@ +package de.deadlocker8.budgetmasterserver.server.charts; + +import static spark.Spark.halt; + +import java.util.ArrayList; + +import org.joda.time.DateTime; + +import com.google.gson.Gson; + +import de.deadlocker8.budgetmaster.logic.Category; +import de.deadlocker8.budgetmaster.logic.CategoryInOutSum; +import de.deadlocker8.budgetmaster.logic.Payment; +import de.deadlocker8.budgetmasterserver.main.DatabaseHandler; +import spark.Request; +import spark.Response; +import spark.Route; + +public class MonthInOutSum implements Route +{ + private DatabaseHandler handler; + private Gson gson; + + public MonthInOutSum(DatabaseHandler handler, Gson gson) + { + this.handler = handler; + this.gson = gson; + } + + @Override + public Object handle(Request req, Response res) throws Exception + { + if(!req.queryParams().contains("startDate") || !req.queryParams().contains("endDate")) + { + halt(400, "Bad Request"); + } + + try + { + DateTime startDate = DateTime.parse(req.queryMap("startDate").value()).withDayOfMonth(1); + DateTime endDate = DateTime.parse(req.queryMap("endDate").value()).withDayOfMonth(1); + + ArrayList<de.deadlocker8.budgetmaster.logic.MonthInOutSum> monthInOutSums = new ArrayList<>(); + + while(startDate.isBefore(endDate) || startDate.isEqual(endDate)) + { + ArrayList<Payment> currentMonthPayments = new ArrayList<>(); + currentMonthPayments.addAll(handler.getPayments(startDate.getYear(), startDate.getMonthOfYear())); + currentMonthPayments.addAll(handler.getRepeatingPayments(startDate.getYear(), startDate.getMonthOfYear())); + + ArrayList<CategoryInOutSum> sums = new ArrayList<>(); + + for(Category currentCategory : handler.getCategories()) + { + sums.add(new CategoryInOutSum(currentCategory.getID(), currentCategory.getName(), currentCategory.getColor(), 0, 0)); + CategoryInOutSum currentInOutSum = sums.get(sums.size() - 1); + for(Payment currentPayment : currentMonthPayments) + { + if(currentCategory.getID() == currentPayment.getCategoryID()) + { + int amount = currentPayment.getAmount(); + if(amount > 0) + { + currentInOutSum.setBudgetIN(currentInOutSum.getBudgetIN() + amount); + } + else + { + currentInOutSum.setBudgetOUT(currentInOutSum.getBudgetOUT() + amount); + } + } + } + } + + monthInOutSums.add(new de.deadlocker8.budgetmaster.logic.MonthInOutSum(startDate.getMonthOfYear(), startDate.getYear(), sums)); + + startDate = startDate.plusMonths(1); + } + + return gson.toJson(monthInOutSums); + } + catch(IllegalStateException ex) + { + halt(500, "Internal Server Error"); + } + + return null; + } +} \ No newline at end of file