Skip to content

Instantly share code, notes, and snippets.

@jeremyorme
Created June 14, 2020 05:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyorme/66a4fa4ca876ed410b5d448780f7e0cd to your computer and use it in GitHub Desktop.
Save jeremyorme/66a4fa4ca876ed410b5d448780f7e0cd to your computer and use it in GitHub Desktop.
Veebe version 2
.rda-progress {
position: relative;
display: block;
height: 25px;
background: rgb(241,241,252);
background: linear-gradient(90deg, rgba(251,251,252,1) 0%, rgba(218,221,236,1) 100%);
overflow: hidden;
}
.nutrient-container {
position: relative;
width: 100%;
}
.nutrient-element {
padding-left: 5px;
padding-top: 2px;
position: absolute;
}
import {Component, h, State} from '@stencil/core';
interface IFoodNutrient {
name: string;
unitName: string;
amount: number;
}
interface IFood {
fdc_id: string;
description: string;
foodNutrients: IFoodNutrient[];
}
interface IIngredientQuantity {
name: string;
number: number;
unit: string;
componentIds: string[];
unitMass: number;
density: number;
}
interface IExpandable {
expanded?: boolean;
}
interface IIngredientQuantityEnriched extends IIngredientQuantity, IExpandable {
components: IFood[];
grams: number;
gramsExplain: string;
nutrientDensity: Map<string, number>;
nutrientGrams: Map<string, number>;
}
interface INutrientValue {
name: string;
value: number;
unit: string;
}
interface IResults {
classified: IIngredientQuantityEnriched[];
aggregated: INutrientValue[];
}
interface IRda {
minAge?: number;
maxAge?: number;
gender?: string;
eq?: number;
lt?: number;
gt?: number;
}
interface INutrientDef extends IExpandable {
name: string;
fdcName?: string;
rdas?: IRda[];
children?: INutrientDef[];
}
interface IAgeRange {
minAge?: number;
maxAge?: number;
}
const age1 = {minAge: 1, maxAge: 1};
const age2To3 = {minAge: 2, maxAge: 3};
const age4To6 = {minAge: 4, maxAge: 6};
const age7To10 = {minAge: 7, maxAge: 10};
const age11To14 = {minAge: 11, maxAge: 14};
const age15To18 = {minAge: 15, maxAge: 18};
const age19To49 = {minAge: 19, maxAge: 49};
const age50To64 = {minAge: 50, maxAge: 64};
const age65To74 = {minAge: 65, maxAge: 74};
const age75Plus = {minAge: 75};
@Component({
tag: 'app-home',
styleUrl: 'app-home.css'
})
export class AppHome {
@State() loading: boolean = false;
@State() recipe: string = '2 courgettes\n1 carrot\n1 avocado';
@State() results: IResults = {classified: [], aggregated: []};
@State() gender: string = 'Male';
@State() ageRange: IAgeRange = age19To49;
@State() portions: number = 1;
units = {
g: { factor: 1.0, base_unit: 'g' },
oz: { factor: 28.3495, base_unit: 'g' },
lb: { factor: 453.592, base_unit: 'g' },
kg: { factor: 1000.0, base_unit: 'g' },
ml: { factor: 1.0, base_unit: 'ml' },
cc: { factor: 1.0, base_unit: 'ml' },
pinch: { factor: 0.73992, base_unit: 'ml' },
tsp: { factor: 5.91939, base_unit: 'ml' },
tbsp: { factor: 17.7582, base_unit: 'ml' },
floz: { factor: 28.4131, base_unit: 'ml' },
cup: { factor: 284.131, base_unit: 'ml' },
pt: { factor: 568.261, base_unit: 'ml' },
dl: { factor: 100.0, base_unit: 'ml' },
l: { factor: 1000.0, base_unit: 'ml' },
gal: { factor: 4546.09, base_unit: 'ml' }
};
// https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/618167/government_dietary_recommendations.pdf
nutrients: INutrientDef[] = [
{name: 'Macronutrients', expanded: true, children: [
{name: 'Protein', fdcName: 'Protein', rdas: [
{minAge: 1, maxAge: 3, eq: 14.5},
{minAge: 4, maxAge: 6, eq: 19.7},
{minAge: 7, maxAge: 10, eq: 28.3},
{minAge: 11, maxAge: 14, gender: 'Male', eq: 42.1},
{minAge: 11, maxAge: 14, gender: 'Female', eq: 41.2},
{minAge: 15, maxAge: 18, gender: 'Male', eq: 55.2},
{minAge: 15, maxAge: 64, gender: 'Female', eq: 45.0},
{minAge: 19, maxAge: 64, gender: 'Male', eq: 55.5},
{minAge: 65, gender: 'Male', eq: 53.3},
{minAge: 65, gender: 'Female', eq: 46.5}
]},
{name: 'Fat', fdcName: 'Total lipid (fat)', rdas: [
{minAge: 4, maxAge: 6, gender: 'Male', lt: 58.0},
{minAge: 4, maxAge: 6, gender: 'Female', lt: 54.0},
{minAge: 7, maxAge: 10, gender: 'Male', lt: 71.0},
{minAge: 7, maxAge: 10, gender: 'Female', lt: 66.0},
{minAge: 11, maxAge: 64, gender: 'Male', lt: 97.0},
{minAge: 11, maxAge: 64, gender: 'Female', lt: 78.0},
{minAge: 65, maxAge: 74, gender: 'Male', lt: 91.0},
{minAge: 65, maxAge: 74, gender: 'Female', lt: 74.0},
{minAge: 75, gender: 'Male', lt: 89.0},
{minAge: 75, gender: 'Female', lt: 72.0}
], children: [
{name: 'Saturated fat', fdcName: 'Fatty acids, total saturated', rdas: [
{minAge: 4, maxAge: 6, gender: 'Male', lt: 18.0},
{minAge: 4, maxAge: 6, gender: 'Female', lt: 17.0},
{minAge: 7, maxAge: 10, gender: 'Male', lt: 22.0},
{minAge: 7, maxAge: 10, gender: 'Female', lt: 21.0},
{minAge: 11, maxAge: 64, gender: 'Male', lt: 31.0},
{minAge: 11, maxAge: 64, gender: 'Female', lt: 24.0},
{minAge: 65, maxAge: 74, gender: 'Male', lt: 29.0},
{minAge: 65, gender: 'Female', lt: 23.0},
{minAge: 75, gender: 'Male', lt: 28.0}
]},
{name: 'Monounsaturated fat', fdcName: 'Fatty acids, total monounsaturated', rdas: [
{minAge: 4, maxAge: 6, gender: 'Male', eq: 21.0},
{minAge: 4, maxAge: 6, gender: 'Female', eq: 20.0},
{minAge: 7, maxAge: 10, gender: 'Male', eq: 26.0},
{minAge: 7, maxAge: 10, gender: 'Female', eq: 25.0},
{minAge: 11, maxAge: 64, gender: 'Male', eq: 36.0},
{minAge: 11, maxAge: 64, gender: 'Female', eq: 29.0},
{minAge: 65, maxAge: 74, gender: 'Male', eq: 34.0},
{minAge: 65, maxAge: 74, gender: 'Female', eq: 28.0},
{minAge: 75, gender: 'Male', eq: 33.0},
{minAge: 75, gender: 'Female', eq: 27.0}
]},
{name: 'Polyunsaturated fat', fdcName: 'Fatty acids, total polyunsaturated', rdas: [
{minAge: 4, maxAge: 6, gender: 'Male', eq: 11.0},
{minAge: 4, maxAge: 6, gender: 'Female', eq: 10.0},
{minAge: 7, maxAge: 10, gender: 'Male', eq: 13.0},
{minAge: 7, maxAge: 10, gender: 'Female', eq: 12.0},
{minAge: 11, maxAge: 64, gender: 'Male', eq: 18.0},
{minAge: 11, maxAge: 74, gender: 'Female', eq: 14.0},
{minAge: 65, gender: 'Male', eq: 17.0},
{minAge: 75, gender: 'Female', eq: 13.0}
]},
]},
{name: 'Carbohydrate', fdcName: 'Carbohydrate, by difference', rdas: [
{minAge: 2, maxAge: 3, gender: 'Male', gt: 145.0},
{minAge: 2, maxAge: 3, gender: 'Female', gt: 134.0},
{minAge: 4, maxAge: 6, gender: 'Male', gt: 198.0},
{minAge: 4, maxAge: 6, gender: 'Female', gt: 184.0},
{minAge: 7, maxAge: 10, gender: 'Male', gt: 242.0},
{minAge: 7, maxAge: 10, gender: 'Female', gt: 227.0},
{minAge: 11, maxAge: 64, gender: 'Male', gt: 333.0},
{minAge: 11, maxAge: 64, gender: 'Female', gt: 267.0},
{minAge: 65, maxAge: 74, gender: 'Male', gt: 312.0},
{minAge: 65, maxAge: 74, gender: 'Female', gt: 255.0},
{minAge: 75, gender: 'Male', gt: 306.0},
{minAge: 75, gender: 'Female', gt: 245.0}
]},
{name: 'Dietary fibre', fdcName: 'Fiber, total dietary', rdas: [
{minAge: 2, maxAge: 4, lt: 15.0},
{minAge: 5, maxAge: 10, lt: 20.0},
{minAge: 11, maxAge: 14, lt: 25.0},
{minAge: 15, lt: 30.0}
]},
]},
{name: 'Vitamins', expanded: true, children: [
{name: 'Vitamin A', fdcName: 'Vitamin A, RAE', rdas: [
{minAge: 1, maxAge: 6, eq: 400.0},
{minAge: 7, maxAge: 10, eq: 500.0},
{minAge: 11, maxAge: 14, gender: 'Male', eq: 600.0},
{minAge: 11, gender: 'Female', eq: 600.0},
{minAge: 15, gender: 'Male', eq: 700.0}
]},
{name: 'Thiamin', fdcName: 'Thiamin', rdas: [
{minAge: 1, maxAge: 1, eq: 0.3},
{minAge: 2, maxAge: 3, eq: 0.4},
{minAge: 4, maxAge: 6, eq: 0.6},
{minAge: 7, maxAge: 10, eq: 0.7},
{minAge: 11, maxAge: 64, gender: 'Male', eq: 1.0},
{minAge: 11, maxAge: 74, gender: 'Female', eq: 0.8},
{minAge: 65, gender: 'Male', eq: 0.9},
{minAge: 75, gender: 'Female', eq: 0.7}
]},
{name: 'Riboflavin', fdcName: 'Riboflavin', rdas: [
{minAge: 1, maxAge: 3, eq: 0.6},
{minAge: 4, maxAge: 6, eq: 0.8},
{minAge: 7, maxAge: 10, eq: 1.0},
{minAge: 11, maxAge: 14, gender: 'Male', eq: 1.2},
{minAge: 11, gender: 'Female', eq: 1.1},
{minAge: 15, gender: 'Male', eq: 1.3}
]},
{name: 'Niacin', fdcName: 'Niacin', rdas: [
{minAge: 1, maxAge: 1, gender: 'Male', eq: 5.0},
{minAge: 1, maxAge: 1, gender: 'Female', eq: 4.7},
{minAge: 2, maxAge: 3, gender: 'Male', eq: 7.2},
{minAge: 2, maxAge: 3, gender: 'Female', eq: 6.6},
{minAge: 4, maxAge: 6, gender: 'Male', eq: 9.8},
{minAge: 4, maxAge: 6, gender: 'Female', eq: 9.1},
{minAge: 7, maxAge: 10, gender: 'Male', eq: 12.0},
{minAge: 7, maxAge: 10, gender: 'Female', eq: 11.2},
{minAge: 11, maxAge: 64, gender: 'Male', eq: 16.5},
{minAge: 11, maxAge: 64, gender: 'Female', eq: 13.2},
{minAge: 65, maxAge: 74, gender: 'Male', eq: 15.5},
{minAge: 65, maxAge: 74, gender: 'Female', eq: 12.6},
{minAge: 75, gender: 'Male', eq: 15.1},
{minAge: 75, gender: 'Female', eq: 12.1}
]},
{name: 'Vitamin B6', fdcName: 'Vitamin B-6', rdas: [
{minAge: 1, maxAge: 3, eq: 0.7},
{minAge: 4, maxAge: 6, eq: 0.9},
{minAge: 7, maxAge: 10, eq: 1.0},
{minAge: 11, maxAge: 14, gender: 'Male', eq: 1.2},
{minAge: 11, maxAge: 14, gender: 'Female', eq: 1.0},
{minAge: 15, maxAge: 18, gender: 'Male', eq: 1.5},
{minAge: 15, gender: 'Female', eq: 1.2},
{minAge: 18, gender: 'Male', eq: 1.4}
]},
{name: 'Vitamin B12', fdcName: 'Vitamin B-12', rdas: [
{minAge: 1, maxAge: 3, eq: 0.5},
{minAge: 4, maxAge: 6, eq: 0.8},
{minAge: 7, maxAge: 10, eq: 1.0},
{minAge: 11, maxAge: 14, eq: 1.2},
{minAge: 15, eq: 1.5}
]},
{name: 'Folate', fdcName: 'Folate, DFE', rdas: [
{minAge: 1, maxAge: 3, eq: 70.0},
{minAge: 4, maxAge: 6, eq: 100.0},
{minAge: 7, maxAge: 10, eq: 150.0},
{minAge: 11, eq: 200.0}
]},
{name: 'Vitamin C', fdcName: 'Vitamin C, total ascorbic acid', rdas: [
{minAge: 1, maxAge: 10, eq: 30.0},
{minAge: 11, maxAge: 14, eq: 35.0},
{minAge: 15, eq: 40.0}
]},
{name: 'Vitamin D', fdcName: 'Vitamin D (D2 + D3)', rdas: [
{minAge: 1, eq: 10.0}
]}
]},
{name: 'Minerals', expanded: true, children: [
{name: 'Iron', fdcName: 'Iron, Fe', rdas: [
{minAge: 1, maxAge: 3, eq: 6.9},
{minAge: 4, maxAge: 6, eq: 6.1},
{minAge: 7, maxAge: 10, eq: 8.7},
{minAge: 11, maxAge: 18, gender: 'Male', eq: 11.3},
{minAge: 11, maxAge: 49, gender: 'Female', eq: 14.8},
{minAge: 19, gender: 'Male', eq: 8.7},
{minAge: 50, gender: 'Female', eq: 8.7}
]},
{name: 'Calcium', fdcName: 'Calcium, Ca', rdas: [
{minAge: 1, maxAge: 3, eq: 350.0},
{minAge: 4, maxAge: 6, eq: 450.0},
{minAge: 7, maxAge: 10, eq: 550.0},
{minAge: 11, maxAge: 18, gender: 'Male', eq: 1000.0},
{minAge: 11, maxAge: 18, gender: 'Female', eq: 800.0},
{minAge: 19, eq: 700.0}
]},
{name: 'Magnesium', fdcName: 'Magnesium, Mg', rdas: [
{minAge: 1, maxAge: 3, eq: 85.0},
{minAge: 4, maxAge: 6, eq: 120.0},
{minAge: 7, maxAge: 10, eq: 200.0},
{minAge: 11, maxAge: 14, eq: 280.0},
{minAge: 15, maxAge: 18, eq: 300.0},
{minAge: 19, gender: 'Male', eq: 300.0},
{minAge: 19, gender: 'Female', eq: 270.0}
]},
{name: 'Potassium', fdcName: 'Potassium, K', rdas: [
{minAge: 1, maxAge: 3, eq: 800.0},
{minAge: 4, maxAge: 6, eq: 1100.0},
{minAge: 7, maxAge: 10, eq: 2000.0},
{minAge: 11, maxAge: 14, eq: 3100.0},
{minAge: 15, eq: 3500.0}
]},
{name: 'Zinc', fdcName: 'Zinc, Zn', rdas: [
{minAge: 1, maxAge: 3, eq: 5.0},
{minAge: 4, maxAge: 6, eq: 6.5},
{minAge: 7, maxAge: 10, eq: 7.0},
{minAge: 11, maxAge: 14, eq: 9.0},
{minAge: 15, gender: 'Male', eq: 9.5},
{minAge: 15, gender: 'Female', eq: 7.0}
]},
{name: 'Copper', fdcName: 'Copper, Cu', rdas: [
{minAge: 1, maxAge: 3, eq: 0.4},
{minAge: 4, maxAge: 6, eq: 0.6},
{minAge: 7, maxAge: 10, eq: 0.7},
{minAge: 11, maxAge: 14, eq: 0.8},
{minAge: 14, maxAge: 18, eq: 1.0},
{minAge: 19, eq: 1.2}
]},
{name: 'Iodine', fdcName: 'Iodine, I', rdas: [
{minAge: 1, maxAge: 3, eq: 70.0},
{minAge: 4, maxAge: 6, eq: 100.0},
{minAge: 7, maxAge: 10, eq: 110.0},
{minAge: 11, maxAge: 14, eq: 130.0},
{minAge: 15, eq: 140.0}
]},
{name: 'Selenium', fdcName: 'Selenium, Se', rdas: [
{minAge: 1, maxAge: 3, eq: 15.0},
{minAge: 4, maxAge: 6, eq: 20.0},
{minAge: 7, maxAge: 10, eq: 30.0},
{minAge: 11, maxAge: 14, eq: 45.0},
{minAge: 15, maxAge: 18, gender: 'Male', eq: 70.0},
{minAge: 15, maxAge: 18, gender: 'Female', eq: 60.0}
]},
{name: 'Phosphorus', fdcName: 'Phosphorus, P', rdas: [
{minAge: 1, maxAge: 3, eq: 270.0},
{minAge: 4, maxAge: 6, eq: 350.0},
{minAge: 7, maxAge: 10, eq: 450.0},
{minAge: 11, maxAge: 18, gender: 'Male', eq: 775.0},
{minAge: 11, maxAge: 18, gender: 'Female', eq: 625.0},
{minAge: 19, eq: 550.0}
]},
{name: 'Chloride', fdcName: 'Chlorine, Cl', rdas: [
{minAge: 1, maxAge: 3, eq: 800.0},
{minAge: 4, maxAge: 6, eq: 1100.0},
{minAge: 7, maxAge: 10, eq: 1800.0},
{minAge: 11, eq: 2500.0}
]},
{name: 'Sodium', fdcName: 'Sodium, Na', rdas: [
{minAge: 1, maxAge: 3, lt: 800},
{minAge: 4, maxAge: 6, lt: 1200},
{minAge: 7, maxAge: 10, lt: 2000},
{minAge: 11, lt: 2400}
]}
]}
];
static async fetchFood(fdcId: string): Promise<IFood> {
let res = await fetch('https://api.nal.usda.gov/fdc/v1/food/' + fdcId + '?api_key=y14zokqvhjz7HfuBypJkYwKwknfKPDowl75n9rRt&format=abridged', {
method: 'GET'
});
return res.json();
}
static async fetchClassified(iqStrings: string[]): Promise<IIngredientQuantity[]> {
let res = await fetch('https://us-central1-nutrition-calculator-264017.cloudfunctions.net/nutrient-table', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({recipe: iqStrings})
});
return res.json();
}
calcAggregated(classified: IIngredientQuantityEnriched[]): INutrientValue[] {
// Calculate the mass in grams of each ingredient
for (let iq of classified) {
if (iq.componentIds.length == 0) {
iq.grams = 0.0;
iq.gramsExplain = 'unrecognised ingredient';
continue;
}
if (iq.unit == 'default') {
iq.grams = iq.number * iq.unitMass / this.portions;
iq.gramsExplain = iq.number + ' * ' + iq.unitMass + 'g' + ' / ' + this.portions;
}
else if (this.units[iq.unit]) {
if (this.units[iq.unit].base_unit == 'g') {
iq.grams = iq.number * this.units[iq.unit].factor / this.portions;
iq.gramsExplain = iq.number + ' ' + iq.unit + ' * ' + this.units[iq.unit].factor + ' g/' + iq.unit + ' / ' + this.portions;
}
else {
iq.grams = iq.number * this.units[iq.unit].factor * iq.density / this.portions;
iq.gramsExplain = iq.number + ' ' + iq.unit + ' * ' + this.units[iq.unit].factor + ' ml/' + iq.unit + ' * ' + iq.density + ' g/ml' + ' / ' + this.portions;
}
}
else {
iq.grams = 0.0;
iq.gramsExplain = 'unrecognised unit';
}
}
// Find the number of ingredients containing each nutrient and the unit the nutrient is denominated in
let componentCount = 0;
let nutrientCounts: Map<string, number> = new Map();
let nutrientUnits: Map<string, string> = new Map();
for (let iq of classified)
for (let c of iq.components) {
componentCount++;
for (let n of c.foodNutrients) {
nutrientCounts.set(n.name, nutrientCounts.get(n.name) ? nutrientCounts.get(n.name) + 1 : 1);
nutrientUnits.set(n.name, n.unitName);
}
}
// Find nutrients that have RDAs
const defs: INutrientDef[] = [...this.nutrients];
let fdcNames: Set<string> = new Set();
while (defs.length > 0) {
const def = defs.pop();
if (def.fdcName)
fdcNames.add(def.fdcName);
if (def.children)
defs.push(...def.children);
}
// For nutrients contained in every ingredient, add an aggregate entry with zero initial value
let aggregated: INutrientValue[] = [];
for (let [n, c] of nutrientCounts.entries())
if (c == componentCount && fdcNames.has(n))
aggregated.push({name: n, value: 0.0, unit: nutrientUnits.get(n)});
// Calculate the mass of each nutrient in each ingredient in grams and aggregate over the ingredients
for (let iq of classified) {
iq.nutrientDensity = new Map();
iq.nutrientGrams = new Map();
for (let n of aggregated) {
let sum = 0.0;
for (let c of iq.components) {
for (let n2 of c.foodNutrients)
if (n.name == n2.name)
sum += n2.amount;
}
const nutrientDensity = sum / iq.components.length;
iq.nutrientDensity.set(n.name, nutrientDensity);
const nutrientGrams = nutrientDensity / 100 * iq.grams;
iq.nutrientGrams.set(n.name, nutrientGrams);
n.value += nutrientGrams;
}
}
return aggregated;
}
async classifyAndAggregate() {
// Invoke cloud function to classify raw ingredient strings
let classified = (await AppHome.fetchClassified(this.recipe.split('\n'))) as IIngredientQuantityEnriched[];
// Fetch FDC data for identified food components
await Promise.all(
classified.map(async iq => {
iq.components = await Promise.all(
iq.componentIds.map(cid => AppHome.fetchFood(cid)))
}));
const aggregated = this.calcAggregated(classified);
// Update the displayed data
this.results = { classified: classified, aggregated: aggregated };
}
aggregate() {
this.results = { classified: this.results.classified, aggregated: this.calcAggregated(this.results.classified) };
}
async updateClicked() {
this.loading = true;
await this.classifyAndAggregate();
this.loading = false;
}
effectiveRda(nutrientDef: INutrientDef) {
for (const rda of nutrientDef.rdas) {
if (rda.gender && this.gender != rda.gender)
continue;
if (rda.minAge && this.ageRange.minAge < rda.minAge)
continue;
if (rda.maxAge && this.ageRange.maxAge > rda.maxAge)
continue;
if (rda.eq)
return rda.eq;
if (rda.lt)
return rda.lt;
if (rda.gt)
return rda.gt;
}
}
calcRda(result: INutrientValue, nutrientDefs: INutrientDef[]) {
for (const nutrientDef of nutrientDefs) {
if (result.name == nutrientDef.fdcName) {
const rda = this.effectiveRda(nutrientDef);
if (rda || rda == 0.0)
return rda;
}
if (nutrientDef.children) {
const rda = this.calcRda(result, nutrientDef.children);
if (rda || rda == 0.0)
return rda;
}
}
}
buildNutrientRdaTitle(name: string, result: INutrientValue) {
const rda = this.calcRda(result, this.nutrients);
return rda || rda == 0.0 ? <span class="nutrient-element">{name} = {result.value.toFixed(1)} {result.unit.toLowerCase()} ({(result.value * 100 / rda).toFixed(0)}% RDA)</span> : <span class="nutrient-element">RDA unavailable</span>;
}
buildRdaProgress(result: INutrientValue) {
const rda = this.calcRda(result, this.nutrients);
return rda || rda == 0.0 ? <span class="nutrient-element rda-progress" style={{width: (result.value * 100 / rda).toFixed(0) + '%'}}/> : <span />;
}
buildNutrientTitle(n: INutrientDef) {
if (!n.fdcName)
return <span>{n.name}</span>;
for (const result of this.results.aggregated) {
if (result.name == n.fdcName) {
return <span class="nutrient-container">{this.buildRdaProgress(result)}{this.buildNutrientRdaTitle(n.name, result)}</span>;
}
}
return <span>{n.name} = (no data)</span>;
}
buildComponentBreakdown(nutrientDef: INutrientDef) {
if (!nutrientDef.fdcName)
return null;
for (const result of this.results.aggregated)
if (result.name == nutrientDef.fdcName)
return this.results.classified.map(iq => <ion-item>
{iq.number} {iq.unit == 'default' ? 'x' : iq.unit.toLowerCase()} {iq.name}: (
{iq.nutrientDensity.get(result.name).toFixed(2)} {result.unit.toLowerCase()}/100g / 100 * {iq.grams.toFixed(2)} g = {iq.nutrientGrams.get(result.name).toFixed(2)} {result.unit.toLowerCase()})</ion-item>);
}
toggleExpanded(n: IExpandable) {
n.expanded = n.expanded ? false : true;
this.results = {...this.results};
}
buildNutrientListItem(n: INutrientDef, level: number) {
const title = this.buildNutrientTitle(n);
const content = n.expanded ? <div>{this.buildComponentBreakdown(n)}{n.children ? n.children.map(c => this.buildNutrientListItem(c, level + 1)) : null}</div> : null;
if (level == 1)
return <div><h2>{title}</h2>{content}</div>;
return <div><ion-item onClick={() => this.toggleExpanded(n)}>{title}</ion-item>{content}</div>;
}
render() {
return [
<ion-header>
<ion-toolbar color="primary">
<ion-title>Veebe</ion-title>
</ion-toolbar>
</ion-header>,
<ion-content class="ion-padding">
<ion-textarea value={this.recipe} onIonChange={e => {this.recipe = e.detail.value}} rows={10} />
<ion-button onClick={() => this.updateClicked()}>Update</ion-button>
<ion-item>
<ion-label>Gender</ion-label>
<ion-select value={this.gender} onIonChange={e => {this.gender = e.detail.value}}>
<ion-select-option>Male</ion-select-option>
<ion-select-option>Female</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label>Age</ion-label>
<ion-select value={this.ageRange} onIonChange={e => {this.ageRange = e.detail.value}}>
<ion-select-option value={age1}>1</ion-select-option>
<ion-select-option value={age2To3}>2 - 3</ion-select-option>
<ion-select-option value={age4To6}>4 - 6</ion-select-option>
<ion-select-option value={age7To10}>7 - 10</ion-select-option>
<ion-select-option value={age11To14}>11 - 14</ion-select-option>
<ion-select-option value={age15To18}>15 - 18</ion-select-option>
<ion-select-option value={age19To49}>19 - 49</ion-select-option>
<ion-select-option value={age50To64}>50 - 64</ion-select-option>
<ion-select-option value={age65To74}>65 - 74</ion-select-option>
<ion-select-option value={age75Plus}>75+</ion-select-option>
</ion-select>
</ion-item>
<ion-item>
<ion-label>Portions</ion-label>
<ion-select value={this.portions} onIonChange={e => {this.portions = e.detail.value; this.aggregate();}}>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(p => <ion-select-option value={p}>{p}</ion-select-option>)}
</ion-select>
</ion-item>
{this.loading ? <p>Loading<br/><ion-spinner name="dots"/></p> :
<div>
{this.results.aggregated.length > 0 ? <div>
<h2>Matched Ingredients</h2>
{this.results.classified.map(iq =>
<div>
<ion-item onClick={() => this.toggleExpanded(iq)}>{iq.number} {iq.unit == 'default' ? 'x' : iq.unit} {iq.name}</ion-item>
{iq.expanded ? <div>
<ion-item>Mass = {iq.gramsExplain} = {iq.grams.toFixed(2)} g</ion-item>
{iq.density > 0.0 ? <ion-item>Density = {iq.density.toFixed(3)} g/ml</ion-item> : null}
{iq.unitMass > 0.0 ? <ion-item>Unit Mass = {iq.unitMass.toFixed(1)} g</ion-item> : null}
<ion-item>FDC Component Foods</ion-item>
{iq.components.map(c => <ion-item>{c.fdc_id} {c.description}</ion-item>)}
<ion-item>Average Nutrient Values</ion-item>
{this.results.aggregated.map(n => <ion-item>{n.name} = {iq.nutrientDensity.get(n.name).toFixed(2)} {n.unit.toLowerCase()} / 100g</ion-item>)}
</div> : null}
</div>)}
{this.nutrients.map(n => this.buildNutrientListItem(n, 1))}
</div> : null}
</div>}
</ion-content>
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment