Macronutrient Triangle by Charles M. Greenspon Clone
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
7.7KB

  1. ### Macro Triangle v2
  2. ### Charles M. Greenspon - charles.greenspon@gmail.com
  3. # Import Pandas & numpy
  4. import numpy as np
  5. import pandas as pd
  6. # Import Bokeh
  7. from bokeh.plotting import figure, show, output_file
  8. from bokeh.layouts import layout, widgetbox, column, row, Spacer
  9. from bokeh.embed import file_html
  10. from bokeh.io import show, output_notebook, export_png
  11. from bokeh.models import Text, CDSView, Plot, Circle, CustomJS, CustomJSFilter, HoverTool, ColumnDataSource, Select, TextInput, TapTool
  12. from bokeh.transform import factor_cmap
  13. from bokeh.models.widgets import Toggle
  14. from bokeh.events import ButtonClick
  15. output_file("MacroTriangleV2.html", title="Macronutrient Proportions")
  16. source_colors = pd.DataFrame()
  17. source_colors['reg'] = ['#4CAF50', '#FF9800', '#9C27B0', '#8D6E63', '#f44336', '#2196F3'] # Regular colors
  18. source_colors['cb'] = ['#673AB7', '#FF5722', '#FFC107', '#3F51B5', '#f44336', '#2196F3']
  19. # Prepare Data
  20. target_cols = ['Food Group', 'Food Name', 'Protein (g)', 'Fat (g)', 'Carbohydrates (g)']
  21. og_data = pd.read_excel("..\USDA-SR28-V1.xlsx", skiprows = 2, header = 1, usecols = target_cols)
  22. groups_of_interest = ['Dairy and Egg Products','Beef Products', 'Breakfast Cereals',
  23. 'Cereal Grains and Pasta', 'Finfish and Shellfish Products',
  24. 'Fruits and Fruit Juices', 'Lamb, Veal, and Game Products',
  25. 'Legumes and Legume Products', 'Nut and Seed Products',
  26. 'Pork Products', 'Poultry Products', 'Sausages and Luncheon Meats',
  27. 'Vegetables and Vegetable Products']
  28. trunc_data = og_data.loc[og_data['Food Group'].isin(groups_of_interest)]
  29. trunc_data['sum'] = trunc_data[['Protein (g)','Carbohydrates (g)','Fat (g)']].sum(axis = 1)
  30. trunc_data['pProtein'] = trunc_data['Protein (g)']/trunc_data['sum']
  31. trunc_data['pCarbs'] = trunc_data['Carbohydrates (g)']/trunc_data['sum']
  32. trunc_data['pFats'] = trunc_data['Fat (g)']/trunc_data['sum']
  33. trunc_data['pCarb_pFats'] = trunc_data['pFats'] - trunc_data['pCarbs']
  34. ratiod_data = trunc_data[['Food Group','Food Name','pCarb_pFats', 'pProtein']]
  35. ratiod_data = ratiod_data.reset_index(drop=True)
  36. ### Data Preparation
  37. output_groups = ['Fruits & Vegetables', 'Cereals & Grains', 'Animal Products',
  38. 'Nuts & Seeds', 'Meat', 'Fish']
  39. meta_groups = []
  40. meta_groups.append(['Fruits and Fruit Juices', 'Legumes and Legume Products',
  41. 'Vegetables and Vegetable Products']) # Fruit & Veg
  42. meta_groups.append(['Breakfast Cereals', 'Cereal Grains and Pasta']) # Cereals & Grains
  43. meta_groups.append(['Dairy and Egg Products']) # Animal Products
  44. meta_groups.append(['Nut and Seed Products']) # Nuts & Seeds
  45. meta_groups.append(['Beef Products', 'Lamb, Veal, and Game Products', 'Pork Products',
  46. 'Poultry Products', 'Sausages and Luncheon Meats']) # Meat
  47. meta_groups.append(['Finfish and Shellfish Products']) # Fish
  48. ratiod_data['Meta Group'] = ""
  49. ratiod_data['def_colors'] = ""
  50. ratiod_data['cb_colors'] = ""
  51. for g in range(len(output_groups)):
  52. ratiod_data["Meta Group"].loc[ratiod_data["Food Group"].str.contains('|'.join(meta_groups[g]))] = output_groups[g]
  53. ratiod_data["def_colors"].loc[ratiod_data["Food Group"].str.contains('|'.join(meta_groups[g]))] = source_colors.loc[g,'reg']
  54. ratiod_data["cb_colors"].loc[ratiod_data["Food Group"].str.contains('|'.join(meta_groups[g]))] = source_colors.loc[g,'cb']
  55. ratiod_data['color'] = ratiod_data["def_colors"]
  56. # Prepare the legend
  57. legend_x = np.ones(len(output_groups)) * -1
  58. legend_y = np.linspace(0.975,0.8,6)
  59. legend_c = {'reg': source_colors['reg'].to_list(), 'cb': source_colors['cb'].to_list()}
  60. legend_source = ColumnDataSource(dict(x=legend_x, y=legend_y, text=output_groups, colors=legend_c['reg']))
  61. legend_glyph = Text(x="x", y="y", text="text", text_align="left", text_color="colors", text_font_size = "10pt")
  62. # Make seaschable data source
  63. unfilt_source = ColumnDataSource(ratiod_data)
  64. filterDataIndices = dict(x=[True for num in range(1, len(unfilt_source.data['Food Name']))])
  65. filtered_source = ColumnDataSource(filterDataIndices)
  66. callback = CustomJS(args=dict(source=filtered_source, sel=unfilt_source), code="""
  67. var textTyped = cb_obj.value.toUpperCase();
  68. var filteredDataSource = source.data['x'];
  69. for (var i = 0; i < sel.get_length(); i++){
  70. if (sel.data['Food Name'][i].toUpperCase().includes(textTyped)){
  71. filteredDataSource[i] = true;
  72. } else if (sel.data['Food Group'][i].toUpperCase().includes(textTyped)){
  73. filteredDataSource[i] = true;
  74. } else {
  75. filteredDataSource[i] = false;
  76. }
  77. }
  78. source.change.emit();
  79. sel.change.emit();
  80. """)
  81. food_search = TextInput(value="", name='foodSearchTxtInput', placeholder="Food Search:",
  82. sizing_mode='scale_width', callback=callback, css_classes=["hide-label"])
  83. custom_filter = CustomJSFilter(args=dict(source=unfilt_source, sel=filtered_source), code='''
  84. return sel.data['x'];
  85. ''')
  86. view = CDSView(source=unfilt_source, filters=[custom_filter])
  87. # Add toggle button for colorblind
  88. clr_callback = CustomJS(args=dict(source=unfilt_source, color_source=legend_c,
  89. leg_source=legend_source), code="""
  90. var state = cb_obj.active
  91. var data = source.data
  92. var leg = leg_source.data
  93. if (state){
  94. data['color'] = data['cb_colors'];
  95. leg['colors'] = color_source['cb'];
  96. } else{
  97. data['color'] = data['def_colors'];
  98. leg['colors'] = color_source['reg'];
  99. }
  100. source.change.emit();
  101. leg_source.change.emit();
  102. """)
  103. color_toggle = Toggle(label="Colorblind Mode", button_type="success", callback = clr_callback)
  104. # Make the plot
  105. p_tools = "hover, wheel_zoom, pan, reset"
  106. p = figure(tools = p_tools, plot_width=775,toolbar_location ="below",
  107. plot_height=700, x_range=(-1.25, 1.25), y_range=(-0.1, 1.1))
  108. #title = "Macronutrient Ratios",
  109. p.hover.tooltips = [("Name", "@{Food Name}")]
  110. p.hover.names = ['dp']
  111. p.add_glyph(legend_source, legend_glyph)
  112. p.circle(x = 'pCarb_pFats', y = 'pProtein', size = 7,
  113. color = 'color', alpha = 0.3, source = unfilt_source, view=view,
  114. name='dp')
  115. p.xgrid.grid_line_color = None
  116. p.ygrid.grid_line_color = None
  117. p.xaxis.visible = False
  118. p.yaxis.visible = False
  119. label_x = [-1.025, 0, 1.025]
  120. label_y = [-0.05, 1.025, -0.05]
  121. label_text = ['Carbohydrate', 'Protein', 'Fat']
  122. label_source = ColumnDataSource(dict(x = label_x, y = label_y, text = label_text))
  123. label_glyph = Text(x="x", y="y", text="text", text_align="center")
  124. p.add_glyph(label_source, label_glyph)
  125. info_x = legend_x[0:2] * -1
  126. info_y = legend_y[0:2]
  127. info_text = ['Charles M. Greenspon', 'USDA/MyFoodData']
  128. info_source = ColumnDataSource(dict(x = info_x, y = info_y, text = info_text))
  129. info_glyph = Text(x="x", y="y", text="text", text_align="right", text_font_size = "10pt", text_color='#757575')
  130. p.add_glyph(info_source, info_glyph, name='info')
  131. tapback = CustomJS(code="""
  132. //console.log('x-position: ' + cb_obj.x)
  133. //console.log('y-position: ' + cb_obj.y)
  134. var x_pos = cb_obj.x
  135. var y_pos = cb_obj.y
  136. if (x_pos >= 0.6 && x_pos <= 1 && y_pos >= 0.975 && y_pos <= 1) {
  137. window.open("http://www.greenspon.science/home");
  138. } else if (x_pos >= 0.63 && x_pos <= 1 && y_pos >= 0.935 && y_pos <= 0.9565){
  139. window.open("https://tools.myfooddata.com/")
  140. }
  141. """)
  142. p.js_on_event('tap', tapback)
  143. # Go
  144. show(column(row(food_search, color_toggle), p))
  145. #export_png(p, filename="plot.png")
  146. ''' Must manually add the below command below the header
  147. <style>
  148. .hide-label label{
  149. display: none !important;
  150. }
  151. </style>
  152. '''