Merge branch 'feature/pie-chart' into 'main'
Pie chart for nutrients See merge request cse1105/2025-2026/teams/csep-team-76!94
This commit is contained in:
commit
b942862dc9
12 changed files with 286 additions and 2 deletions
|
|
@ -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.
|
||||
* <p>
|
||||
* This accumulates all the nutrients (properly scaled) from all ingredients in this recipe
|
||||
* and returns a summary with data points.
|
||||
* </p>
|
||||
* <p>
|
||||
* The list will be of length 3 if any nutrients are added, or 0 if all the nutrients
|
||||
* sum up to be 0.
|
||||
* </p>
|
||||
* @return The data for this pie chart, as a labeled list of data.
|
||||
*/
|
||||
private List<PieChart.Data> 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.
|
||||
* <p>
|
||||
* The pie chart will disappear if all the nutrients in the recipe
|
||||
* are zero.
|
||||
* </p>
|
||||
* @param recipe The recipe to display.
|
||||
*/
|
||||
public void setRecipe(Recipe recipe) {
|
||||
this.recipe = recipe;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
List<PieChart.Data> 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<PieChart.Data> fresh = this.getPieChartData();
|
||||
List<PieChart.Data> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> langSelector;
|
||||
|
||||
@FXML
|
||||
private NutritionPieChartCtrl pieChartController;
|
||||
|
||||
private ListView<Recipe> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.chart.PieChart?>
|
||||
|
||||
|
||||
<PieChart fx:controller="client.scenes.nutrition.NutritionPieChartCtrl" fx:id="pieChart" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" />
|
||||
|
|
@ -42,6 +42,11 @@
|
|||
<!-- Preparation -->
|
||||
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||
<Label fx:id="inferredServeSizeLabel" />
|
||||
<Label fx:id="inferredKcalLabel" />
|
||||
<HBox spacing="20">
|
||||
<VBox spacing="0">
|
||||
<Label fx:id="inferredServeSizeLabel" />
|
||||
<Label fx:id="inferredKcalLabel" />
|
||||
</VBox>
|
||||
<fx:include fx:id="pieChart" source="../nutrition/NutritionPieChart.fxml" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ menu.label.selected-langs=Języki
|
|||
|
||||
menu.shopping.title=Lista zakupów
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue