Add macro breakdown bar with 3-color visualization#51
Conversation
Creates a visual breakdown bar showing carbs (blue), protein (green), and fat (orange) proportions based on caloric contribution. Includes labels showing gram amounts and percentages for each macro. Features: - Proportional width based on calories (carbs/protein = 4cal/g, fat = 9cal/g) - Color-coded sections with smooth transitions - Individual labels with colored indicators - Displays both grams and percentage for each macro 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Summary of Changes
Hello @grepsedawk, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a new visual component to display the breakdown of macronutrients (carbs, protein, and fat) based on their caloric contribution. This bar provides a quick, color-coded overview of macro proportions, enhancing the user's ability to track their nutritional intake.
Highlights
- New Macro Breakdown Bar: I've implemented a
createMacroBreakdownBarmethod that generates a visual representation of carbohydrate, protein, and fat proportions. - Caloric Proportionality: The bar's sections are proportionally sized based on the caloric contribution of each macro (4 cal/g for carbs/protein, 9 cal/g for fat).
- Visual Cues and Labels: The bar features distinct color-coded sections (blue for carbs, green for protein, orange for fat) with smooth transitions, accompanied by labels showing both gram amounts and percentage contributions for each macro.
- Integration into ShowTotals: The newly created macro breakdown bar is now integrated into the
ShowTotalscomponent, appearing alongside existing progress indicators.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
|
🚀 PR Preview deployed! You can preview this PR at: https://grepsedawk.github.io/planmy.hike/pr-51/ The preview will update automatically when you push new commits to this PR. This comment was automatically generated by the PR Preview workflow. |
There was a problem hiding this comment.
Pull Request Overview
This PR adds a visual macro breakdown bar to the food planning interface that displays carbohydrate, protein, and fat proportions based on their caloric contributions. The visualization uses a three-color bar chart with blue for carbs, green for protein, and orange for fat, along with detailed labels showing both gram amounts and percentages.
Key Changes
- Adds a new macro breakdown visualization component to the food planning totals section
- Implements proportional width calculation based on macro caloric values (carbs/protein = 4cal/g, fat = 9cal/g)
- Creates color-coded labels with indicators showing grams and percentages for each macronutrient
| // Calculate percentages | ||
| const carbPercent = totalMacroCalories > 0 ? (carbCalories / totalMacroCalories) * 100 : 0 | ||
| const proteinPercent = totalMacroCalories > 0 ? (proteinCalories / totalMacroCalories) * 100 : 0 | ||
| const fatPercent = totalMacroCalories > 0 ? (fatCalories / totalMacroCalories) * 100 : 0 |
There was a problem hiding this comment.
Division by zero protection is inconsistent. If totalMacroCalories is 0, all percentages will be 0, but the bar sections will still have width styles applied. This could lead to unexpected visual behavior when no foods are added.
| // Calculate percentages | |
| const carbPercent = totalMacroCalories > 0 ? (carbCalories / totalMacroCalories) * 100 : 0 | |
| const proteinPercent = totalMacroCalories > 0 ? (proteinCalories / totalMacroCalories) * 100 : 0 | |
| const fatPercent = totalMacroCalories > 0 ? (fatCalories / totalMacroCalories) * 100 : 0 | |
| // If there are no macro calories, do not render the bar | |
| if (totalMacroCalories === 0) { | |
| return null | |
| } | |
| // Calculate percentages | |
| const carbPercent = (carbCalories / totalMacroCalories) * 100 | |
| const proteinPercent = (proteinCalories / totalMacroCalories) * 100 | |
| const fatPercent = (fatCalories / totalMacroCalories) * 100 |
| // Label | ||
| const labelEl = document.createElement("div") | ||
| labelEl.style.cssText = | ||
| "font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--text-primary); margin-bottom: var(--spacing-2);" | ||
| labelEl.textContent = "Macro Breakdown" | ||
|
|
||
| // Bar container | ||
| const barContainer = document.createElement("div") | ||
| barContainer.style.cssText = ` | ||
| display: flex; | ||
| height: 24px; | ||
| background-color: var(--background-tertiary); | ||
| border-radius: var(--radius-md); | ||
| overflow: hidden; | ||
| ` | ||
|
|
||
| // Carbs section | ||
| const carbsSection = document.createElement("div") | ||
| carbsSection.style.cssText = ` | ||
| background-color: var(--primary); | ||
| width: ${carbPercent}%; | ||
| transition: all 0.3s ease; | ||
| ` | ||
|
|
||
| // Protein section | ||
| const proteinSection = document.createElement("div") | ||
| proteinSection.style.cssText = ` | ||
| background-color: var(--success); | ||
| width: ${proteinPercent}%; | ||
| transition: all 0.3s ease; | ||
| ` | ||
|
|
||
| // Fat section | ||
| const fatSection = document.createElement("div") | ||
| fatSection.style.cssText = ` | ||
| background-color: var(--warning); | ||
| width: ${fatPercent}%; | ||
| transition: all 0.3s ease; | ||
| ` | ||
|
|
There was a problem hiding this comment.
When carbPercent is 0, the CSS width will be '0%' which is valid, but when totalMacroCalories is 0, all three sections will have 0% width, potentially causing layout issues. Consider adding a check to hide the entire bar when no macro data exists.
| // Label | |
| const labelEl = document.createElement("div") | |
| labelEl.style.cssText = | |
| "font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--text-primary); margin-bottom: var(--spacing-2);" | |
| labelEl.textContent = "Macro Breakdown" | |
| // Bar container | |
| const barContainer = document.createElement("div") | |
| barContainer.style.cssText = ` | |
| display: flex; | |
| height: 24px; | |
| background-color: var(--background-tertiary); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| ` | |
| // Carbs section | |
| const carbsSection = document.createElement("div") | |
| carbsSection.style.cssText = ` | |
| background-color: var(--primary); | |
| width: ${carbPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Protein section | |
| const proteinSection = document.createElement("div") | |
| proteinSection.style.cssText = ` | |
| background-color: var(--success); | |
| width: ${proteinPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Fat section | |
| const fatSection = document.createElement("div") | |
| fatSection.style.cssText = ` | |
| background-color: var(--warning); | |
| width: ${fatPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Only render macro breakdown bar if there is macro data | |
| if (totalMacroCalories > 0) { | |
| // Label | |
| const labelEl = document.createElement("div") | |
| labelEl.style.cssText = | |
| "font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); color: var(--text-primary); margin-bottom: var(--spacing-2);" | |
| labelEl.textContent = "Macro Breakdown" | |
| // Bar container | |
| const barContainer = document.createElement("div") | |
| barContainer.style.cssText = ` | |
| display: flex; | |
| height: 24px; | |
| background-color: var(--background-tertiary); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| ` | |
| // Carbs section | |
| const carbsSection = document.createElement("div") | |
| carbsSection.style.cssText = ` | |
| background-color: var(--primary); | |
| width: ${carbPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Protein section | |
| const proteinSection = document.createElement("div") | |
| proteinSection.style.cssText = ` | |
| background-color: var(--success); | |
| width: ${proteinPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Fat section | |
| const fatSection = document.createElement("div") | |
| fatSection.style.cssText = ` | |
| background-color: var(--warning); | |
| width: ${fatPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // (Presumably, code to append these elements to the DOM follows here) | |
| } |
There was a problem hiding this comment.
Code Review
This pull request introduces a new macro breakdown bar, which is a valuable visualization. The implementation is functional, but there are several opportunities for improvement in terms of maintainability, security, and code quality. My review includes suggestions to refactor duplicated code, replace 'magic numbers' with constants, and avoid using innerHTML for better security. These changes will make the new component more robust and easier to maintain.
| const carbLabel = document.createElement("div") | ||
| carbLabel.style.cssText = "display: flex; align-items: center; gap: var(--spacing-1);" | ||
| carbLabel.innerHTML = ` | ||
| <div style="width: 12px; height: 12px; background-color: var(--primary); border-radius: 2px;"></div> | ||
| <span>Carbs: ${totalCarbs.toFixed(1)}g (${carbPercent.toFixed(0)}%)</span> | ||
| ` | ||
|
|
||
| const proteinLabel = document.createElement("div") | ||
| proteinLabel.style.cssText = "display: flex; align-items: center; gap: var(--spacing-1);" | ||
| proteinLabel.innerHTML = ` | ||
| <div style="width: 12px; height: 12px; background-color: var(--success); border-radius: 2px;"></div> | ||
| <span>Protein: ${totalProtein.toFixed(1)}g (${proteinPercent.toFixed(0)}%)</span> | ||
| ` | ||
|
|
||
| const fatLabel = document.createElement("div") | ||
| fatLabel.style.cssText = "display: flex; align-items: center; gap: var(--spacing-1);" | ||
| fatLabel.innerHTML = ` | ||
| <div style="width: 12px; height: 12px; background-color: var(--warning); border-radius: 2px;"></div> | ||
| <span>Fat: ${totalFat.toFixed(1)}g (${fatPercent.toFixed(0)}%)</span> | ||
| ` |
There was a problem hiding this comment.
The creation of these labels is repetitive and uses innerHTML, which can be a security risk (Cross-Site Scripting). It's better to create a helper function that programmatically builds the DOM elements using document.createElement and textContent. This makes the code safer, more maintainable, and eliminates duplication.
const createMacroLabel = (name, total, percent, colorVar) => {
const label = document.createElement("div");
label.style.cssText = "display: flex; align-items: center; gap: var(--spacing-1);";
const colorBox = document.createElement("div");
colorBox.style.cssText = `width: 12px; height: 12px; background-color: ${colorVar}; border-radius: 2px;`;
label.appendChild(colorBox);
const text = document.createElement("span");
text.textContent = `${name}: ${total.toFixed(1)}g (${percent.toFixed(0)}%)`;
label.appendChild(text);
return label;
};
const carbLabel = createMacroLabel("Carbs", totalCarbs, carbPercent, "var(--primary)");
const proteinLabel = createMacroLabel("Protein", totalProtein, proteinPercent, "var(--success)");
const fatLabel = createMacroLabel("Fat", totalFat, fatPercent, "var(--warning)");| const carbCalories = totalCarbs * 4 | ||
| const proteinCalories = totalProtein * 4 | ||
| const fatCalories = totalFat * 9 |
There was a problem hiding this comment.
These hardcoded calorie values are 'magic numbers'. Defining them as named constants makes the code more readable and easier to maintain. You could define a constant object at the top of the function and use it here:
const CALORIES_PER_GRAM = { CARB: 4, PROTEIN: 4, FAT: 9 };| const carbCalories = totalCarbs * 4 | |
| const proteinCalories = totalProtein * 4 | |
| const fatCalories = totalFat * 9 | |
| const carbCalories = totalCarbs * CALORIES_PER_GRAM.CARB; | |
| const proteinCalories = totalProtein * CALORIES_PER_GRAM.PROTEIN; | |
| const fatCalories = totalFat * CALORIES_PER_GRAM.FAT; |
| const carbsSection = document.createElement("div") | ||
| carbsSection.style.cssText = ` | ||
| background-color: var(--primary); | ||
| width: ${carbPercent}%; | ||
| transition: all 0.3s ease; | ||
| ` | ||
|
|
||
| // Protein section | ||
| const proteinSection = document.createElement("div") | ||
| proteinSection.style.cssText = ` | ||
| background-color: var(--success); | ||
| width: ${proteinPercent}%; | ||
| transition: all 0.3s ease; | ||
| ` | ||
|
|
||
| // Fat section | ||
| const fatSection = document.createElement("div") | ||
| fatSection.style.cssText = ` | ||
| background-color: var(--warning); | ||
| width: ${fatPercent}%; | ||
| transition: all 0.3s ease; | ||
| ` | ||
|
|
||
| barContainer.appendChild(carbsSection) | ||
| barContainer.appendChild(proteinSection) | ||
| barContainer.appendChild(fatSection) |
There was a problem hiding this comment.
The creation of the bar sections for carbs, protein, and fat is very repetitive. This can be refactored into a loop to reduce duplication and improve maintainability. This also makes it easier to add or remove macro sections in the future.
Additionally, this approach still uses inline styles. For better separation of concerns, consider moving these styles to a dedicated CSS class.
| const carbsSection = document.createElement("div") | |
| carbsSection.style.cssText = ` | |
| background-color: var(--primary); | |
| width: ${carbPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Protein section | |
| const proteinSection = document.createElement("div") | |
| proteinSection.style.cssText = ` | |
| background-color: var(--success); | |
| width: ${proteinPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| // Fat section | |
| const fatSection = document.createElement("div") | |
| fatSection.style.cssText = ` | |
| background-color: var(--warning); | |
| width: ${fatPercent}%; | |
| transition: all 0.3s ease; | |
| ` | |
| barContainer.appendChild(carbsSection) | |
| barContainer.appendChild(proteinSection) | |
| barContainer.appendChild(fatSection) | |
| const macros = [ | |
| { percent: carbPercent, color: 'var(--primary)' }, | |
| { percent: proteinPercent, color: 'var(--success)' }, | |
| { percent: fatPercent, color: 'var(--warning)' }, | |
| ]; | |
| macros.forEach(macro => { | |
| const section = document.createElement("div"); | |
| section.style.cssText = ` | |
| background-color: ${macro.color}; | |
| width: ${macro.percent}%; | |
| transition: all 0.3s ease; | |
| `; | |
| barContainer.appendChild(section); | |
| }); |
|
Please fix the changes suggested by gemini |
Creates a visual breakdown bar showing carbs (blue), protein (green),
and fat (orange) proportions based on caloric contribution. Includes
labels showing gram amounts and percentages for each macro.
Features:
🤖 Generated with Claude Code
Co-Authored-By: Claude [email protected]