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.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" />
|
||||
<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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue