diff --git a/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java b/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java
index 96b5e1ec28bfd2e9709d31124e6d7b28bbb371e8..0312b0d152190d9b3d0ca185c3cc4a123223ae29 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java
@@ -50,6 +50,10 @@ public class DefaultCharts
 			getChartFromFile("charts/IncomesAndExpendituresPerYearBar.js"),
 			ChartType.DEFAULT, 6, ChartDisplayType.BAR, ChartGroupType.YEAR, "incomesAndExpendituresPerYearBar.png");
 
+	private static final Chart CHART_INCOMES_AND_EXPENDITURES_PER_YEAR_BY_CATEGORIES = new Chart("charts.default.incomesAndExpendituresPerYearByCategories",
+			getChartFromFile("charts/IncomesAndExpendituresPerYearByCategories.js"),
+			ChartType.DEFAULT, 2, ChartDisplayType.BAR, ChartGroupType.YEAR, "incomesAndExpendituresPerYearByCategories.png");
+
 	private DefaultCharts()
 	{
 	}
@@ -65,6 +69,7 @@ public class DefaultCharts
 		charts.add(CHART_INCOMES_AND_EXPENDITURES_PER_MONTH_BY_CATEGORIES);
 		charts.add(CHART_REST_PER_MONTH);
 		charts.add(CHART_INCOMES_AND_EXPENDITURES_PER_YEAR_BAR);
+		charts.add(CHART_INCOMES_AND_EXPENDITURES_PER_YEAR_BY_CATEGORIES);
 		return charts;
 	}
 
@@ -83,7 +88,7 @@ public class DefaultCharts
 		}
 		catch(IOException e)
 		{
-			e.printStackTrace();
+			LOGGER.error("Error getting chart from file", e);
 		}
 
 		return "";
diff --git a/src/main/resources/charts/IncomesAndExpendituresPerYearByCategories.js b/src/main/resources/charts/IncomesAndExpendituresPerYearByCategories.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a8b55dccddcabaa5d8338853323aa6c5715f977
--- /dev/null
+++ b/src/main/resources/charts/IncomesAndExpendituresPerYearByCategories.js
@@ -0,0 +1,205 @@
+/* This list will be dynamically filled with all the transactions between
+ * the start and and date you select on the "Show Chart" page
+ * and filtered according to your specified filter.
+ * An example entry for this list and tutorial about how to create custom charts ca be found in the BudgetMaster wiki:
+ * https://github.com/deadlocker8/BudgetMaster/wiki/How-to-create-custom-charts
+ */
+var transactionData = [];
+
+// Note: All variables starting with "localized" are only available inside default charts.
+
+transactionData = transactionData.reverse();
+
+moment.locale('de');
+
+const NAME = 0;
+const COLOR = 1;
+const INCOME = 2;
+const EXPENDITURE = 3;
+
+var categoryNames = [];
+var categoryColors = [];
+
+for(var i = 0; i < transactionData.length; i++)
+{
+    var currentTransaction = transactionData[i];
+    if(!categoryNames.includes(currentTransaction.category.name))
+    {
+        categoryNames.push(currentTransaction.category.name);
+        categoryColors.push(currentTransaction.category.color);
+    }
+}
+
+var dates = [];
+var values = [];
+
+for(var i = 0; i < transactionData.length; i++)
+{
+    var transaction = transactionData[i];
+
+    var date = moment(transaction.date).startOf('year').format('YYYY');
+    if(!dates.includes(date))
+    {
+        dates.push(date);
+        values.push([
+            categoryNames, // NAME
+            categoryColors, // COLOR
+            new Array(categoryNames.length).fill(0), // INCOME
+            new Array(categoryNames.length).fill(0)  // EXPENDITURE
+        ]);
+    }
+
+    // determine index of category name in list
+    var lastIndex = values.length - 1;
+
+    var categoryName = transaction.category.name;
+    // create new category if not already in dict
+    if(!values[lastIndex][NAME].includes(categoryName))
+    {
+        values[lastIndex][NAME].push(categoryName);
+        values[lastIndex][COLOR].push(transaction.category.color);
+        values[lastIndex][INCOME].push(0);
+        values[lastIndex][EXPENDITURE].push(0);
+    }
+
+    // determine index of category in current last values
+    var index = values[lastIndex][NAME].indexOf(categoryName);
+
+    // add to income or expenditure sum
+    var amount = transaction.amount;
+    if(amount > 0)
+    {
+        values[lastIndex][INCOME][index] = values[lastIndex][INCOME][index] + amount;
+    }
+    else
+    {
+        values[lastIndex][EXPENDITURE][index] = values[lastIndex][EXPENDITURE][index] + Math.abs(amount);
+    }
+}
+
+var totalIncomeSums = [];
+var totalExpenditureSums = [];
+
+// calculate total sums for all months
+for(var i = 0; i < dates.length; i++)
+{
+    var totalIncomes = 0;
+    var totalExpenditures = 0;
+
+    values[i][INCOME].forEach(function(value)
+    {
+        totalIncomes += value;
+    });
+
+    values[i][EXPENDITURE].forEach(function(value)
+    {
+        totalExpenditures += value;
+    });
+
+    totalIncomeSums.push(totalIncomes);
+    totalExpenditureSums.push(totalExpenditures);
+}
+
+// Prepare your chart settings here (mandatory)
+var plotlyData = [];
+var plotlyLayout = {
+    title: {
+        text: localizedTitle
+    },
+    barmode: "stack",
+    hovermode: 'closest', // show hover popup only for hovered item
+    yaxis: {
+        rangemode: 'tozero',
+        tickformat: '.0f',
+        ticksuffix: localizedCurrency,
+        showline: true
+    }
+};
+
+// create one stacked bar for incomes and one for expenditures for every month and group them by month
+for(var i = 0; i < dates.length; i++)
+{
+    for(var j = 0; j < values[i][NAME].length; j++)
+    {
+        var currentValues = values[i];
+        var currentName = currentValues[NAME][j];
+
+        var currentIncomeValue = currentValues[INCOME][j];
+        var percentageIncome = (100 / totalIncomeSums[i]) * currentIncomeValue;
+        var textIncome = prepareHoverText(currentName, percentageIncome, currentIncomeValue);
+
+        var currentExpenditureValue = currentValues[EXPENDITURE][j];
+        var percentageExpenditure = (100 / totalExpenditureSums[i]) * currentExpenditureValue;
+        var textExpenditure = prepareHoverText(currentName, percentageExpenditure, currentExpenditureValue);
+
+        // add border if category color is white
+        var borderWidth = 0;
+        if(currentValues[COLOR][j] === '#FFFFFF')
+        {
+            borderWidth = 1;
+        }
+
+        plotlyData.push({
+            x: [localizedData['label2'], localizedData['label1']],
+            y: [currentIncomeValue / 100.0, currentExpenditureValue / 100.0],
+            type: 'bar',
+            hoverinfo: 'text',
+            hovertext: [textIncome, textExpenditure],
+            name: currentName,
+            xaxis: 'x' + (i + 1),  // for grouping incomes and expenditure bar by month
+            barmode: 'stack',
+            showlegend: i === 0,
+            legendgroup: currentName,
+            marker: {
+                color: currentValues[COLOR][j],  // use the category's color
+                line: {
+                    color: '#212121',
+                    width: borderWidth
+                }
+            }
+        });
+    }
+
+    // axis number inside layout uses a different counting in comparison to xaxis definition in plotlyDate
+    var axisNumber = i + 1;
+    if(i === 0)
+    {
+        axisNumber = '';
+    }
+
+    // calculate subplot start and end position (relative between 0 and 1)
+    var width = 1 / dates.length;
+    var start = i * width;
+    var end = (i + 1) * width;
+
+    plotlyLayout['xaxis' + axisNumber] = {
+        domain: [start, end],
+        anchor: 'x' + axisNumber,
+        title: dates[i],
+    }
+}
+
+// Add your Plotly configuration settings here (optional)
+var plotlyConfig = {
+    showSendToCloud: false,
+    displaylogo: false,
+    showLink: false,
+    responsive: true,
+    displayModeBar: true,
+    toImageButtonOptions: {
+        format: 'png',
+        filename: 'BudgetMaster_chart_export',
+        height: 1080,
+        width: 1920,
+    }
+};
+
+// Don't touch this line
+Plotly.newPlot("containerID", plotlyData, plotlyLayout, plotlyConfig);
+
+
+function prepareHoverText(categoryName, percentage, value)
+{
+    value = value / 100;
+    return categoryName + ' ' + percentage.toFixed(1) + '% (' + value.toFixed(1) + ' ' + localizedCurrency + ')';
+}
\ No newline at end of file
diff --git a/src/main/resources/languages/base_de.properties b/src/main/resources/languages/base_de.properties
index 2d2af29a2e1c726072ddbf1514f6855a9b2b075d..7326352f86bfbf8043e8249847f1484cb2899995 100644
--- a/src/main/resources/languages/base_de.properties
+++ b/src/main/resources/languages/base_de.properties
@@ -533,6 +533,8 @@ charts.default.restPerMonth=Rest
 charts.default.restPerMonth.localization='{"label1": "Rest in "'}
 charts.default.incomesAndExpendituresPerYearBar=Eingaben/Ausgaben
 charts.default.incomesAndExpendituresPerYearBar.localization='{"axisY": "Summe in ", "traceName1": "Einnahmen", "traceName2": "Ausgaben"'}
+charts.default.incomesAndExpendituresPerYearByCategories=Eingaben/Ausgaben nach Kategorien
+charts.default.incomesAndExpendituresPerYearByCategories.localization='{"label1": "Ausgaben", "label2": "Einnahmen"'}
 
 chart.new.label.name=Name
 chart.new.label.script=Script
diff --git a/src/main/resources/languages/base_en.properties b/src/main/resources/languages/base_en.properties
index da0b7609b43d93f421b0378f12a05fced342b316..b0152ba938ad0a4d26c908c7c0b954e92e69ef88 100644
--- a/src/main/resources/languages/base_en.properties
+++ b/src/main/resources/languages/base_en.properties
@@ -533,6 +533,8 @@ charts.default.restPerMonth=Rest
 charts.default.restPerMonth.localization='{"label1": "Rest in "'}
 charts.default.incomesAndExpendituresPerYearBar=Incomes/Expenditures
 charts.default.incomesAndExpendituresPerYearBar.localization='{"axisY": "Sum in ", "traceName1": "Incomes", "traceName2": "Expenditures"'}
+charts.default.incomesAndExpendituresPerYearByCategories=Incomes/Expenditures by categories
+charts.default.incomesAndExpendituresPerYearByCategories.localization='{"label1": "Expenditures", "label2": "Incomes"'}
 
 chart.new.label.name=Name
 chart.new.label.script=Script
diff --git a/src/main/resources/static/images/charts/incomesAndExpendituresPerYearByCategories.png b/src/main/resources/static/images/charts/incomesAndExpendituresPerYearByCategories.png
new file mode 100644
index 0000000000000000000000000000000000000000..d66432ed5b449551296290a09ca3f1db5ee43d24
Binary files /dev/null and b/src/main/resources/static/images/charts/incomesAndExpendituresPerYearByCategories.png differ