Source code for usda_fdc.analysis.visualization

"""
Visualization tools for nutrient analysis.

This module provides functions for visualizing nutrient data, including
macronutrient distribution, nutrient comparison, and DRI percentages.
"""

from typing import Dict, List, Optional, Union, Any, Tuple
import json

from .analysis import NutrientAnalysis


[docs]def generate_macronutrient_chart_data(analysis: NutrientAnalysis) -> Dict[str, Any]: """ Generate data for a macronutrient distribution chart. Args: analysis: The nutrient analysis. Returns: A dictionary with chart data. """ return { "type": "pie", "data": { "labels": ["Protein", "Fat", "Carbohydrate"], "datasets": [{ "data": [ analysis.macronutrient_distribution.get("protein", 0), analysis.macronutrient_distribution.get("fat", 0), analysis.macronutrient_distribution.get("carbohydrate", 0) ], "backgroundColor": [ "#FF6384", "#36A2EB", "#FFCE56" ] }] }, "options": { "title": { "display": True, "text": f"Macronutrient Distribution for {analysis.food.description}" } } }
[docs]def generate_dri_chart_data( analysis: NutrientAnalysis, nutrient_ids: Optional[List[str]] = None, min_percent: float = 1.0 ) -> Dict[str, Any]: """ Generate data for a DRI percentage chart. Args: analysis: The nutrient analysis. nutrient_ids: The nutrient IDs to include (all with DRI values if None). min_percent: The minimum DRI percentage to include. Returns: A dictionary with chart data. """ # Get nutrients with DRI values nutrients_with_dri = { nutrient_id: value for nutrient_id, value in analysis.nutrients.items() if value.dri_percent is not None and value.dri_percent >= min_percent } # Filter by nutrient_ids if provided if nutrient_ids is not None: nutrients_with_dri = { nutrient_id: value for nutrient_id, value in nutrients_with_dri.items() if nutrient_id in nutrient_ids } # Sort by DRI percentage sorted_nutrients = sorted( nutrients_with_dri.items(), key=lambda x: x[1].dri_percent or 0, reverse=True ) # Generate chart data labels = [value.nutrient.display_name for _, value in sorted_nutrients] data = [value.dri_percent or 0 for _, value in sorted_nutrients] return { "type": "horizontalBar", "data": { "labels": labels, "datasets": [{ "label": "% of DRI", "data": data, "backgroundColor": "rgba(54, 162, 235, 0.5)", "borderColor": "rgba(54, 162, 235, 1)", "borderWidth": 1 }] }, "options": { "title": { "display": True, "text": f"DRI Percentages for {analysis.food.description}" }, "scales": { "xAxes": [{ "ticks": { "beginAtZero": True } }] } } }
[docs]def generate_nutrient_comparison_chart_data( analyses: List[NutrientAnalysis], nutrient_id: str ) -> Dict[str, Any]: """ Generate data for a nutrient comparison chart. Args: analyses: The nutrient analyses to compare. nutrient_id: The nutrient ID to compare. Returns: A dictionary with chart data. """ # Get the nutrient values for each food labels = [] data = [] for analysis in analyses: nutrient_value = analysis.get_nutrient(nutrient_id) if nutrient_value: labels.append(analysis.food.description) data.append(nutrient_value.amount) # Get the nutrient display name nutrient_name = "Unknown" for analysis in analyses: nutrient_value = analysis.get_nutrient(nutrient_id) if nutrient_value: nutrient_name = nutrient_value.nutrient.display_name break return { "type": "bar", "data": { "labels": labels, "datasets": [{ "label": f"{nutrient_name} Content", "data": data, "backgroundColor": "rgba(75, 192, 192, 0.5)", "borderColor": "rgba(75, 192, 192, 1)", "borderWidth": 1 }] }, "options": { "title": { "display": True, "text": f"{nutrient_name} Comparison" }, "scales": { "yAxes": [{ "ticks": { "beginAtZero": True } }] } } }
[docs]def generate_nutrient_radar_chart_data( analysis: NutrientAnalysis, nutrient_ids: List[str] ) -> Dict[str, Any]: """ Generate data for a nutrient radar chart. Args: analysis: The nutrient analysis. nutrient_ids: The nutrient IDs to include. Returns: A dictionary with chart data. """ # Get the nutrient values labels = [] data = [] for nutrient_id in nutrient_ids: nutrient_value = analysis.get_nutrient(nutrient_id) if nutrient_value and nutrient_value.dri_percent is not None: labels.append(nutrient_value.nutrient.display_name) # Cap at 100% for better visualization data.append(min(nutrient_value.dri_percent, 100)) return { "type": "radar", "data": { "labels": labels, "datasets": [{ "label": analysis.food.description, "data": data, "backgroundColor": "rgba(54, 162, 235, 0.2)", "borderColor": "rgba(54, 162, 235, 1)", "pointBackgroundColor": "rgba(54, 162, 235, 1)", "pointBorderColor": "#fff", "pointHoverBackgroundColor": "#fff", "pointHoverBorderColor": "rgba(54, 162, 235, 1)" }] }, "options": { "title": { "display": True, "text": f"Nutrient Profile for {analysis.food.description}" }, "scale": { "ticks": { "beginAtZero": True, "max": 100 } } } }
[docs]def generate_html_report(analysis: NutrientAnalysis) -> str: """ Generate an HTML report for a nutrient analysis. Args: analysis: The nutrient analysis. Returns: An HTML string. """ # Generate chart data macro_chart_data = generate_macronutrient_chart_data(analysis) dri_chart_data = generate_dri_chart_data(analysis) # Convert chart data to JSON macro_chart_json = json.dumps(macro_chart_data) dri_chart_json = json.dumps(dri_chart_data) # Generate HTML html = f""" <!DOCTYPE html> <html> <head> <title>Nutrient Analysis: {analysis.food.description}</title> <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script> <style> body {{ font-family: Arial, sans-serif; margin: 20px; }} .container {{ max-width: 1200px; margin: 0 auto; }} .chart-container {{ width: 100%; max-width: 600px; margin: 20px auto; }} table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }} th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }} th {{ background-color: #f2f2f2; }} tr:nth-child(even) {{ background-color: #f9f9f9; }} </style> </head> <body> <div class="container"> <h1>Nutrient Analysis: {analysis.food.description}</h1> <p>FDC ID: {analysis.food.fdc_id}</p> <p>Data Type: {analysis.food.data_type}</p> <p>Serving Size: {analysis.serving_size}g</p> <p>Calories: {analysis.calories_per_serving:.1f} kcal</p> <h2>Macronutrient Distribution</h2> <div class="chart-container"> <canvas id="macroChart"></canvas> </div> <h2>Nutrient Content</h2> <div class="chart-container"> <canvas id="driChart"></canvas> </div> <h2>Detailed Nutrient Information</h2> <table> <tr> <th>Nutrient</th> <th>Amount</th> <th>% of DRI</th> </tr> """ # Add macronutrients html += "<tr><th colspan='3'>Macronutrients</th></tr>" for nutrient_id in ["protein", "fat", "carbohydrate", "fiber"]: nutrient_value = analysis.get_nutrient(nutrient_id) if nutrient_value: dri_percent = f"{nutrient_value.dri_percent:.1f}%" if nutrient_value.dri_percent is not None else "N/A" html += f""" <tr> <td>{nutrient_value.nutrient.display_name}</td> <td>{nutrient_value.amount:.1f} {nutrient_value.unit}</td> <td>{dri_percent}</td> </tr> """ # Add vitamins html += "<tr><th colspan='3'>Vitamins</th></tr>" for nutrient_id, nutrient_value in analysis.get_vitamins().items(): dri_percent = f"{nutrient_value.dri_percent:.1f}%" if nutrient_value.dri_percent is not None else "N/A" html += f""" <tr> <td>{nutrient_value.nutrient.display_name}</td> <td>{nutrient_value.amount:.1f} {nutrient_value.unit}</td> <td>{dri_percent}</td> </tr> """ # Add minerals html += "<tr><th colspan='3'>Minerals</th></tr>" for nutrient_id, nutrient_value in analysis.get_minerals().items(): dri_percent = f"{nutrient_value.dri_percent:.1f}%" if nutrient_value.dri_percent is not None else "N/A" html += f""" <tr> <td>{nutrient_value.nutrient.display_name}</td> <td>{nutrient_value.amount:.1f} {nutrient_value.unit}</td> <td>{dri_percent}</td> </tr> """ # Close the table and add chart initialization html += f""" </table> </div> <script> // Initialize macronutrient chart var macroCtx = document.getElementById('macroChart').getContext('2d'); var macroChart = new Chart(macroCtx, {macro_chart_json}); // Initialize DRI chart var driCtx = document.getElementById('driChart').getContext('2d'); var driChart = new Chart(driCtx, {dri_chart_json}); </script> </body> </html> """ return html