feat: pie chart for nutrients
This commit is contained in:
parent
167a80c84a
commit
c1301dad28
4 changed files with 230 additions and 2 deletions
|
|
@ -0,0 +1,210 @@
|
||||||
|
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(
|
||||||
|
"Protein",
|
||||||
|
info.protein()
|
||||||
|
),
|
||||||
|
new PieChart.Data(
|
||||||
|
"Fat",
|
||||||
|
info.fat()
|
||||||
|
),
|
||||||
|
new PieChart.Data(
|
||||||
|
"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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocaleManager getLocaleManager() {
|
||||||
|
return this.localeManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data in this pie chart.
|
||||||
|
*/
|
||||||
|
private 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);
|
||||||
|
this.pieChart.getData().setAll(
|
||||||
|
this.getPieChartData()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.exception.UpdateException;
|
||||||
import client.scenes.FoodpalApplicationCtrl;
|
import client.scenes.FoodpalApplicationCtrl;
|
||||||
|
import client.scenes.nutrition.NutritionPieChartCtrl;
|
||||||
import client.service.ShoppingListService;
|
import client.service.ShoppingListService;
|
||||||
import client.utils.Config;
|
import client.utils.Config;
|
||||||
import client.utils.ConfigService;
|
import client.utils.ConfigService;
|
||||||
|
|
@ -106,6 +107,9 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
@FXML
|
@FXML
|
||||||
private ComboBox<String> langSelector;
|
private ComboBox<String> langSelector;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private NutritionPieChartCtrl pieChartController;
|
||||||
|
|
||||||
private ListView<Recipe> getParentRecipeList() {
|
private ListView<Recipe> getParentRecipeList() {
|
||||||
return this.appCtrl.recipeList;
|
return this.appCtrl.recipeList;
|
||||||
}
|
}
|
||||||
|
|
@ -185,11 +189,14 @@ public class RecipeDetailCtrl implements LocaleAware {
|
||||||
// expose the scaled view to list controllers
|
// expose the scaled view to list controllers
|
||||||
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||||
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||||
|
this.pieChartController.setRecipe(recipe);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ingredientListController.refetchFromRecipe(recipe);
|
this.ingredientListController.refetchFromRecipe(recipe);
|
||||||
this.stepListController.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 -->
|
<!-- Preparation -->
|
||||||
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
||||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||||
|
<HBox spacing="20">
|
||||||
|
<VBox spacing="0">
|
||||||
<Label fx:id="inferredServeSizeLabel" />
|
<Label fx:id="inferredServeSizeLabel" />
|
||||||
<Label fx:id="inferredKcalLabel" />
|
<Label fx:id="inferredKcalLabel" />
|
||||||
</VBox>
|
</VBox>
|
||||||
|
<fx:include fx:id="pieChart" source="../nutrition/NutritionPieChart.fxml" />
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue