diff --git a/client/src/main/java/client/scenes/nutrition/NutritionPieChartCtrl.java b/client/src/main/java/client/scenes/nutrition/NutritionPieChartCtrl.java
new file mode 100644
index 0000000..a90a677
--- /dev/null
+++ b/client/src/main/java/client/scenes/nutrition/NutritionPieChartCtrl.java
@@ -0,0 +1,234 @@
+package client.scenes.nutrition;
+
+import client.utils.LocaleAware;
+import client.utils.LocaleManager;
+import com.google.inject.Inject;
+import commons.FormalIngredient;
+import commons.Ingredient;
+import commons.Recipe;
+import commons.VagueIngredient;
+import javafx.fxml.FXML;
+import javafx.scene.chart.PieChart;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+public class NutritionPieChartCtrl implements LocaleAware {
+ /**
+ * Nutrition info for a recipe or an ingredient.
+ *
+ * @param protein The protein this recipe/ingredient has
+ * @param fat The fat this recipe/ingredient has
+ * @param carbs The carbs this recipe/ingredient has
+ */
+ private record CompleteNutritionInfo(
+ double protein,
+ double fat,
+ double carbs
+ ) {
+ /**
+ * Create a new {@link CompleteNutritionInfo} with zeroed values.
+ *
+ * @return A {@link CompleteNutritionInfo} with zeroed values.
+ */
+ static CompleteNutritionInfo zero() {
+ return new CompleteNutritionInfo(0.0, 0.0, 0.0);
+ }
+
+ /**
+ * Check whether this instance has all values set to zero.
+ *
+ * @return Whether all values (protein, carbs, fat) are zero.
+ */
+ public boolean isZero() {
+ return this.protein() == 0.0
+ && this.carbs() == 0.0
+ && this.fat() == 0.0;
+ }
+
+ /**
+ * Scale this object by an amount. Multiplies all values by amount.
+ *
+ * @param amount The amount to scale it by.
+ * @return The newly scaled nutrition info.
+ */
+ public CompleteNutritionInfo scaled(double amount) {
+ return new CompleteNutritionInfo(
+ this.protein() * amount,
+ this.fat() * amount,
+ this.carbs() * amount
+ );
+ }
+
+ /**
+ * Add another nutrition info object to this object.
+ *
+ * @param rhs The nutrition info object to add.
+ * @return A new nutrition info object with the sum of both objects' nutrients.
+ */
+ public CompleteNutritionInfo add(CompleteNutritionInfo rhs) {
+ return new CompleteNutritionInfo(
+ this.protein() + rhs.protein(),
+ this.fat() + rhs.fat(),
+ this.carbs() + rhs.carbs()
+ );
+ }
+ }
+
+ private final LocaleManager localeManager;
+ private final Logger logger = Logger.getLogger(NutritionPieChartCtrl.class.getName());
+
+ @FXML
+ PieChart pieChart;
+
+ private Recipe recipe;
+
+ @Inject
+ NutritionPieChartCtrl(
+ LocaleManager manager
+ ) {
+ this.localeManager = manager;
+ }
+
+ /**
+ * Get the data for this pie chart based on the current recipe.
+ *
+ * This accumulates all the nutrients (properly scaled) from all ingredients in this recipe
+ * and returns a summary with data points.
+ *
+ *
+ * The list will be of length 3 if any nutrients are added, or 0 if all the nutrients
+ * sum up to be 0.
+ *
+ * @return The data for this pie chart, as a labeled list of data.
+ */
+ private List getPieChartData() {
+ CompleteNutritionInfo info = this.recipe
+ .getIngredients()
+ .stream()
+ .map(ri -> {
+ Ingredient i = ri.getIngredient();
+ logger.info("Mapping ingredient " + i.toString());
+
+ switch (ri) {
+ case FormalIngredient fi -> {
+ return new CompleteNutritionInfo(
+ i.getProteinPer100g(),
+ i.getFatPer100g(),
+ i.getCarbsPer100g()
+ )
+ .scaled(fi.getBaseAmount());
+ }
+ case VagueIngredient vi -> {
+ return new CompleteNutritionInfo(
+ i.getProteinPer100g(),
+ i.getFatPer100g(),
+ i.getCarbsPer100g()
+ )
+ .scaled(vi.getBaseAmount());
+ }
+ default -> {
+ return null;
+ }
+ }
+ })
+ .filter(Objects::nonNull)
+ .reduce(CompleteNutritionInfo::add)
+ .orElseGet(CompleteNutritionInfo::zero);
+
+ this.logger.info( "Updated data: " + info.toString() );
+
+ if (info.isZero()) {
+ return List.of();
+ }
+
+ return List.of(
+ new PieChart.Data(
+ this.getLocaleString("menu.nutrition.protein"),
+ info.protein()
+ ),
+ new PieChart.Data(
+ this.getLocaleString("menu.nutrition.fat"),
+ info.fat()
+ ),
+ new PieChart.Data(
+ this.getLocaleString("menu.nutrition.carbs"),
+ info.carbs()
+ )
+ );
+ }
+
+ /**
+ * Set the current recipe to be displayed in the pie chart.
+ * The pie chart will be refreshed.
+ *
+ * The pie chart will disappear if all the nutrients in the recipe
+ * are zero.
+ *
+ * @param recipe The recipe to display.
+ */
+ public void setRecipe(Recipe recipe) {
+ this.recipe = recipe;
+ this.refresh();
+ }
+
+ @Override
+ public void updateText() {
+ List data = this.pieChart.getData();
+ if (data.isEmpty()) return;
+ final int EXPECTED_DATA_SIZE = 3;
+ if (data.size() != EXPECTED_DATA_SIZE) return;
+
+ data.get(0).setName(this.getLocaleString("menu.nutrition.protein"));
+ data.get(1).setName(this.getLocaleString("menu.nutrition.fat"));
+ final int TWO = 2;
+ data.get(TWO).setName(this.getLocaleString("menu.nutrition.carbs"));
+ }
+
+ @Override
+ public LocaleManager getLocaleManager() {
+ return this.localeManager;
+ }
+
+ /**
+ * Refresh the data in this pie chart.
+ */
+ @SuppressWarnings("checkstyle:MagicNumber")
+ public void refresh() {
+ if (this.recipe == null) {
+ this.pieChart.setVisible(false);
+ logger.info("Refreshing pie chart with no recipe");
+ return;
+ }
+
+ logger.info("Refreshing pie chart with recipe");
+ this.pieChart.setVisible(true);
+ if (this.pieChart.getData().isEmpty()) {
+ this.pieChart.getData().setAll(
+ this.getPieChartData()
+ );
+ } else {
+ List fresh = this.getPieChartData();
+ List data = this.pieChart.getData();
+
+ if (fresh.isEmpty()) {
+ data.clear();
+ return;
+ }
+
+ data.get(0).setPieValue(fresh.get(0).getPieValue());
+ data.get(1).setPieValue(fresh.get(1).getPieValue());
+ final int TWO = 2;
+ data.get(TWO).setPieValue(fresh.get(TWO).getPieValue());
+ }
+ }
+
+ public void initializeComponents() {
+ final double START_ANGLE = 60.0;
+
+ this.pieChart.setClockwise(true);
+ this.pieChart.setStartAngle(START_ANGLE);
+ this.refresh();
+ }
+}
diff --git a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java
index cacccb9..37c8d50 100644
--- a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java
+++ b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java
@@ -2,6 +2,7 @@ package client.scenes.recipe;
import client.exception.UpdateException;
import client.scenes.FoodpalApplicationCtrl;
+import client.scenes.nutrition.NutritionPieChartCtrl;
import client.service.ShoppingListService;
import client.utils.Config;
import client.utils.ConfigService;
@@ -106,6 +107,9 @@ public class RecipeDetailCtrl implements LocaleAware {
@FXML
private ComboBox langSelector;
+ @FXML
+ private NutritionPieChartCtrl pieChartController;
+
private ListView getParentRecipeList() {
return this.appCtrl.recipeList;
}
@@ -185,11 +189,14 @@ public class RecipeDetailCtrl implements LocaleAware {
// expose the scaled view to list controllers
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
+ this.pieChartController.setRecipe(recipe);
return;
}
this.ingredientListController.refetchFromRecipe(recipe);
this.stepListController.refetchFromRecipe(recipe);
+
+ this.pieChartController.setRecipe(recipe);
}
/**
diff --git a/client/src/main/resources/client/scenes/nutrition/NutritionPieChart.fxml b/client/src/main/resources/client/scenes/nutrition/NutritionPieChart.fxml
new file mode 100644
index 0000000..d63531b
--- /dev/null
+++ b/client/src/main/resources/client/scenes/nutrition/NutritionPieChart.fxml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml
index 602b827..355486c 100644
--- a/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml
+++ b/client/src/main/resources/client/scenes/recipe/RecipeDetailView.fxml
@@ -42,6 +42,11 @@
-
-
+
+
+
+
+
+
+
diff --git a/client/src/main/resources/locale/lang.properties b/client/src/main/resources/locale/lang.properties
index 59f8bb1..0fc1d46 100644
--- a/client/src/main/resources/locale/lang.properties
+++ b/client/src/main/resources/locale/lang.properties
@@ -38,6 +38,10 @@ menu.search=Search...
menu.label.selected-langs=Languages
+menu.nutrition.carbs=Carbohydrates
+menu.nutrition.protein=Protein
+menu.nutrition.fat=Fat
+
lang.en.display=English
lang.nl.display=Dutch
lang.pl.display=Polish
diff --git a/client/src/main/resources/locale/lang_en.properties b/client/src/main/resources/locale/lang_en.properties
index bf41079..fe24dec 100644
--- a/client/src/main/resources/locale/lang_en.properties
+++ b/client/src/main/resources/locale/lang_en.properties
@@ -40,6 +40,10 @@ menu.label.selected-langs=Languages
menu.shopping.title=Shopping list
+menu.nutrition.carbs=Carbohydrates
+menu.nutrition.protein=Protein
+menu.nutrition.fat=Fat
+
lang.en.display=English
lang.nl.display=Dutch
lang.pl.display=Polish
diff --git a/client/src/main/resources/locale/lang_nl.properties b/client/src/main/resources/locale/lang_nl.properties
index f944251..0a31475 100644
--- a/client/src/main/resources/locale/lang_nl.properties
+++ b/client/src/main/resources/locale/lang_nl.properties
@@ -38,6 +38,10 @@ menu.label.selected-langs=Talen
menu.shopping.title=Boodschappenlijst
+menu.nutrition.carbs=Koolhydraten
+menu.nutrition.protein=Eiwitten
+menu.nutrition.fat=Vetten
+
menu.search=Zoeken...
lang.en.display=Engels
lang.nl.display=Nederlands
diff --git a/client/src/main/resources/locale/lang_pl.properties b/client/src/main/resources/locale/lang_pl.properties
index 28695ad..0809cbf 100644
--- a/client/src/main/resources/locale/lang_pl.properties
+++ b/client/src/main/resources/locale/lang_pl.properties
@@ -40,6 +40,10 @@ menu.label.selected-langs=Języki
menu.shopping.title=Lista zakupw
+menu.nutrition.carbs=W?glowodany
+menu.nutrition.protein=Bia?ka
+menu.nutrition.fat=T?uszcze
+
lang.en.display=Inglisz
lang.nl.display=Holenderski
lang.pl.display=Polski
diff --git a/client/src/main/resources/locale/lang_tok.properties b/client/src/main/resources/locale/lang_tok.properties
index 14cc3cd..00f73b6 100644
--- a/client/src/main/resources/locale/lang_tok.properties
+++ b/client/src/main/resources/locale/lang_tok.properties
@@ -40,6 +40,10 @@ menu.label.selected-langs=toki wile
menu.shopping.title=ijo wile mani mute
+menu.nutrition.carbs=kipisi moku suwi
+menu.nutrition.protein=kipisi moku walo
+menu.nutrition.fat=kipisi moku suli
+
lang.en.display=toki Inli
lang.nl.display=toki Netelan
lang.pl.display=toki Posuka
diff --git a/client/src/main/resources/locale/lang_tr.properties b/client/src/main/resources/locale/lang_tr.properties
index 4af12b8..5fae2e1 100644
--- a/client/src/main/resources/locale/lang_tr.properties
+++ b/client/src/main/resources/locale/lang_tr.properties
@@ -40,6 +40,10 @@ menu.label.selected-langs=Diller
menu.shopping.title=Al??veri? listesi
+menu.nutrition.carbs=Karbonhidratlar
+menu.nutrition.protein=Protein
+menu.nutrition.fat=Ya?
+
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski
diff --git a/client/src/main/resources/locale/lang_zhc.properties b/client/src/main/resources/locale/lang_zhc.properties
index 646ce54..a306cd5 100644
--- a/client/src/main/resources/locale/lang_zhc.properties
+++ b/client/src/main/resources/locale/lang_zhc.properties
@@ -29,6 +29,10 @@ menu.search=搜索
menu.label.selected-langs=语言
+menu.nutrition.carbs=?????
+menu.nutrition.protein=???
+menu.nutrition.fat=??
+
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski
diff --git a/client/src/main/resources/locale/lang_zht.properties b/client/src/main/resources/locale/lang_zht.properties
index fe5c3fb..67d8c46 100644
--- a/client/src/main/resources/locale/lang_zht.properties
+++ b/client/src/main/resources/locale/lang_zht.properties
@@ -29,6 +29,10 @@ menu.search=搜索
menu.label.selected-langs=語言
+menu.nutrition.carbs=?????
+menu.nutrition.protein=???
+menu.nutrition.fat=??
+
lang.en.display=English
lang.nl.display=Nederlands
lang.pl.display=Polski