Skip to content

Instantly share code, notes, and snippets.

@Bounga
Forked from alphapapa/fitness.org
Created March 15, 2019 08:30
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 Bounga/8e8943b83b8d9086c66baafd72786f0a to your computer and use it in GitHub Desktop.
Save Bounga/8e8943b83b8d9086c66baafd72786f0a to your computer and use it in GitHub Desktop.
An Emacs food/weight/workout tracker self-contained in a single Org file

Plots

/home/me/org/double-plot.png

Tasks

Data

Food

Food data

!DateFoodCaloriesProteinPriceComment
[2018-07-19 Wed]Chef Boyardee: Lasagna (1 can)460181.00
[2018-07-19 Wed]Chef Boyardee: Double-stuffed beef ravioli (1 can)460201.00
[2018-07-18 Tue]Hardee’s: Natural-Cut French Fries (small)30040.00
[2018-07-18 Tue]Hardee’s: Cherry Coke (medium)26000.00
[2018-07-18 Tue]Hardee’s: Baby Back Rib Thickburger (1/3 lb.)1040479.29
[2018-07-18 Tue]Kroger: spreadable butter w/canola oil (0.5 tbsp)5000.00
[2018-07-18 Tue]Kroger: apricot preserves (1 tbsp)4000.00
[2018-07-18 Tue]Thomas: blueberry bagel27090.67
[2018-07-18 Tue]Kroger: cheddar cheese (1 slice)8050.20
[2018-07-18 Tue]Kroger: canned pulled pork (1/2 of 10 oz. can)105211.33
[2018-07-18 Tue]Ballpark: Park’s Finest beef frank16060.00
[2018-07-18 Tue]Nature’s Own: whole wheat hot dog bun11050.40
[2018-07-18 Tue]Nature’s Own: honey wheat bread (2 slices)14060.32
[2018-07-17 Mon]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-07-17 Mon]Chick-Fil-A: Sweet Tea (large)17000.00
[2018-07-17 Mon]Chick-Fil-A: Classic Chicken Sandwich440280.00
[2018-07-17 Mon]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-07-17 Mon]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-07-17 Mon]Chick-Fil-A: Grilled Market Salad320267.25
[2018-07-16 Sun]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-07-16 Sun]Arby’s: Sweet Tea (small)10000.00
[2018-07-16 Sun]Wendy’s: Fries (small)32050.00
[2018-07-16 Sun]Wendy’s: Baconator (no ketchup)940598.59
[2018-07-16 Sun]Kroger: spreadable butter w/canola oil (0.5 tbsp)5000.00
[2018-07-16 Sun]Kroger: apricot preserves (1 tbsp)4000.00
[2018-07-16 Sun]Thomas: blueberry bagel27090.67
[2018-07-16 Sun]Kroger: cheddar cheese (1 slice)8050.20
[2018-07-16 Sun]Kroger: canned pulled pork (1/2 of 10 oz. can)105211.33
[2018-07-16 Sun]Sara Lee: Sweet Hawaiian Sandwich Roll (1 bun)17050.34
[2018-07-15 Sat]Arby’s: Sweet Tea (small)10000.00
[2018-07-15 Sat]Arby’s: Mozzarella Sticks (2 piece)2209.51.30
[2018-07-15 Sat]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-07-15 Sat]Arby’s: Triple Chocolate Cookie45051.00
[2018-07-15 Sat]Arby’s: Salted Caramel & Chocolate Cookie43041.00
[2018-07-14 Fri]Ballpark: Park’s Finest beef frank16060.00
[2018-07-14 Fri]Ballpark: Park’s Finest beef frank16060.00
[2018-07-14 Fri]Nature’s Own: whole wheat hot dog bun11050.40
[2018-07-14 Fri]Nature’s Own: whole wheat hot dog bun11050.40
[2018-07-14 Fri]Kroger: spreadable butter w/canola oil (0.5 tbsp)5000.00
[2018-07-14 Fri]Kroger: apricot preserves (1 tbsp)4000.00
[2018-07-14 Fri]Thomas: blueberry bagel27090.67
[2018-07-13 Thu]Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan)29030.78
[2018-07-13 Thu]Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan)29030.78
[2018-07-13 Thu]Wendy’s: Fries (small)32050.00
[2018-07-13 Thu]Wendy’s: Baconator (no ketchup)940598.59
[2018-07-13 Thu]Wendy’s: Dr. Pepper (medium)19000.00
[2018-07-13 Thu]Chick-Fil-A: Chick-n-Minis (3 count)270142.35
[2018-07-13 Thu]Chick-Fil-A: Hash Rounds (1 box)24021.19
[2018-07-13 Thu]Chick-Fil-A: Spicy Chicken Breakfast Burrito450303.90
[2018-07-13 Thu]Chick-Fil-A: Dr. Pepper (medium)18001.59
[2018-07-12 Wed]Tai Pei: Pepper Beef (1 package, 283g)420132.50
[2018-07-12 Wed]Kroger: spreadable butter w/canola oil (0.5 tbsp)5000.00
[2018-07-12 Wed]Kroger: apricot preserves (1 tbsp)4000.00
[2018-07-12 Wed]Thomas: blueberry bagel27090.67
[2018-07-12 Wed]Arby’s: Sweet Tea (small)10000.00
[2018-07-12 Wed]Arby’s: Mozzarella Sticks (2 piece)2209.51.30
[2018-07-12 Wed]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-07-11 Tue]Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan)29030.78
[2018-07-11 Tue]Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan)29030.78
[2018-07-11 Tue]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-07-11 Tue]Hardee’s: Natural-Cut French Fries (small)30040.00
[2018-07-11 Tue]Hardee’s: 1/3 lb. Frisco Thickburger840435.00
[2018-07-10 Mon]Papa John’s: Large pizza, extra sauce, 2x pepperoni (1 slice)350110.00
[2018-07-10 Mon]Papa John’s: Large pizza, extra sauce, 2x pepperoni (1 slice)350110.00
[2018-07-10 Mon]Wendy’s: Bacon Queso Chicken Sandwich590375.49
[2018-07-10 Mon]Wendy’s: Strawberry Mango Chicken Salad (w/2 dressing packets)470396.89
[2018-07-09 Sun]Arby’s: Triple Chocolate Cookie45051.00
[2018-07-09 Sun]Arby’s: Salted Caramel & Chocolate Cookie43041.00
[2018-07-09 Sun]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-07-09 Sun]Arby’s: Sweet Tea (small)10000.00
[2018-07-09 Sun]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-07-09 Sun]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-07-08 Sat]Wendy’s: Fries (small)32050.00
[2018-07-08 Sat]Wendy’s: Baconator (no ketchup)940598.59
[2018-07-08 Sat]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-07-08 Sat]Chick-Fil-A: Greek yogurt parfait230123.06
[2018-07-08 Sat]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-07-08 Sat]Chick-Fil-A: Sweet Tea (large)17000.00
[2018-07-08 Sat]Chick-Fil-A: Spicy Chicken Sandwich490303.35
[2018-07-08 Sat]Chick-Fil-A: Waffle Potato Fries (large)40051.89
[2018-07-07 Fri]Wendy’s: Bacon Queso Chicken Sandwich590378.77
[2018-07-07 Fri]Wendy’s: Fries (small)32050.00
[2018-07-07 Fri]Wendy’s: Dr. Pepper (large)24000.55
[2018-07-07 Fri]Arby’s: Sweet Tea (small)10000.00
[2018-07-07 Fri]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-07-07 Fri]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-07-06 Thu]Arby’s: Sweet Tea (small)10000.00
[2018-07-06 Thu]Wendy’s: Fries (small)32050.00
[2018-07-06 Thu]Wendy’s: Baconator (no ketchup)940598.59
[2018-07-06 Thu]Zaxby’s: Chocolate Chip Cookie17020.00
[2018-07-06 Thu]Zaxby’s: Chocolate Chip Cookie17020.00
[2018-07-05 Wed]Zaxby’s: Chicken Finger (1 finger)100110.00
[2018-07-05 Wed]Zaxby’s: Chicken Finger (1 finger)100110.00
[2018-07-05 Wed]Zaxby’s: Crinkle Fries (w/sandwich meal)37040.00
[2018-07-05 Wed]Zaxby’s: Texas Toast (1 piece)15030.00
[2018-07-05 Wed]Zaxby’s: Sweet Tea (small)27000.00
[2018-07-05 Wed]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-07-05 Wed]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-07-04 Tue]Zaxby’s: Crinkle Fries (w/sandwich meal)37040.00
[2018-07-04 Tue]Zaxby’s: Honey Mustard (1 portion cup)33010.00
[2018-07-04 Tue]Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal)230260.00
[2018-07-04 Tue]Zaxby’s: Sweet Tea (large)51000.00
[2018-07-04 Tue]Zaxby’s: Brownie36040.99
[2018-07-04 Tue]Chick-Fil-A: Chick-n-Minis (4 count)350193.09
[2018-07-04 Tue]Chick-Fil-A: Hash Rounds (1 box)24021.19
[2018-07-04 Tue]Chick-Fil-A: Spicy Chicken Breakfast Burrito450303.90
[2018-07-04 Tue]Chick-Fil-A: Dr. Pepper (large)26001.89
[2018-07-03 Mon]Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice)305110.00
[2018-07-03 Mon]Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice)305110.00
[2018-07-03 Mon]Kroger: spreadable butter w/canola oil (0.5 tbsp)5000.00
[2018-07-03 Mon]Quaker: Steel Cut oatmeal w/blueberries, cranberries (1 packet)17040.34
[2018-07-03 Mon]Thomas: English muffin, blueberry16050.61
[2018-07-03 Mon]Kroger: apricot preserves (1 tbsp)4000.00
[2018-07-02 Sun]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-02 Sun]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-02 Sun]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-02 Sun]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-02 Sun]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-02 Sun]Dr. Pepper: from 2-liter bottle (12 oz/355 ml)15000.00
[2018-07-02 Sun]Dr. Pepper: from 2-liter bottle (12 oz/355 ml)15000.00
[2018-07-02 Sun]Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece)17070.00
[2018-07-02 Sun]Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece)17070.00
[2018-07-02 Sun]Domino’s: Ultimate Pepperoni (1 slice, pan, 12”)340131.25
[2018-07-02 Sun]Domino’s: Ultimate Pepperoni (1 slice, pan, 12”)340131.25
[2018-07-02 Sun]Dr. Pepper: from 2-liter bottle (12 oz/355 ml)15000.00
[2018-07-02 Sun]Dr. Pepper: from 2-liter bottle (12 oz/355 ml)15000.00
[2018-07-01 Sat]Dr. Pepper: from 2-liter bottle (12 oz/355 ml)15000.00
[2018-07-01 Sat]Dr. Pepper: from 2-liter bottle (12 oz/355 ml)15000.00
[2018-07-01 Sat]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-01 Sat]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-01 Sat]Domino’s: Cinnamon Bread Twist (1 piece)25050
[2018-07-01 Sat]Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece)17070
[2018-07-01 Sat]Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece)17070
[2018-07-01 Sat]Domino’s: Ultimate Pepperoni (1 slice, pan, 12”)340131.25
[2018-07-01 Sat]Domino’s: Ultimate Pepperoni (1 slice, pan, 12”)340131.25
[2018-07-01 Sat]Domino’s: Ultimate Pepperoni (1 slice, pan, 12”)340131.25
[2018-06-30 Fri]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-30 Fri]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-30 Fri]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-06-29 Thu]Hardee’s: Cherry Coke (medium)26000.00
[2018-06-29 Thu]Hardee’s: Natural-Cut French Fries (small)36040.00
[2018-06-29 Thu]Hardee’s: Baby Back Rib Thickburger (1/3 lb.)1040479.29
[2018-06-29 Thu]Nature Valley: Crunchy Oats & Dark Chocolate granola bar19030.50
[2018-06-29 Thu]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-29 Thu]Nature Valley: Protein Chewy Bar, peanut butter dark chocolate190100.70
[2018-06-29 Thu]Nature Valley: Dark Chocolate, Peanut & Almond granola bar16030.50
[2018-06-28 Wed]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-28 Wed]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-28 Wed]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830580.00
[2018-06-27 Tue]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-27 Tue]Nature Valley: Protein Chewy Bar, peanut butter dark chocolate190100.70
[2018-06-27 Tue]Nature Valley: Dark Chocolate, Peanut & Almond granola bar16030.50
[2018-06-27 Tue]Arby’s: Sweet Tea (small)10000.00
[2018-06-27 Tue]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-27 Tue]Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich830588.29
[2018-06-26 Mon]Nature Valley: Dark Chocolate, Peanut & Almond granola bar16030.50
[2018-06-26 Mon]Sonic: Pretzel Dog, Original340122.29
[2018-06-26 Mon]Sonic: Pretzel Dog, Original340122.29
[2018-06-26 Mon]Arby’s: Sweet Tea (small)10000.00
[2018-06-26 Mon]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-26 Mon]Arby’s: Double Roast Beef sandwich510384.69
[2018-06-25 Sun]Wendy’s: Fries (small)32050.00
[2018-06-25 Sun]Wendy’s: Baconator (no ketchup)940598.59
[2018-06-25 Sun]Wendy’s: Coca-Cola (small)20000.00
[2018-06-25 Sun]Zaxby’s: Crinkle Fries (w/sandwich meal)37040.00
[2018-06-25 Sun]Zaxby’s: Honey Mustard (1 portion cup)33010.00
[2018-06-25 Sun]Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal)230260.00
[2018-06-25 Sun]Zaxby’s: Sweet Tea (large)51000.00
[2018-06-25 Sun]Zaxby’s: Texas Toast (1 piece)15030.00
[2018-06-24 Sat]Zaxby’s: Crinkle Fries (w/sandwich meal)37040.00
[2018-06-24 Sat]Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal)230260.00
[2018-06-24 Sat]Zaxby’s: Honey Mustard (1 portion cup)33010.00
[2018-06-24 Sat]Arby’s: Sweet Tea (small)10000.00
[2018-06-24 Sat]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-24 Sat]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-24 Sat]Arby’s: Half-Pound Roast Beef sandwich610485.69
[2018-06-23 Fri]Zaxby’s: Sweet Tea (small, 22 oz)27000.00
[2018-06-23 Fri]Zaxby’s: Crinkle Fries (w/sandwich meal)37040.00
[2018-06-23 Fri]Zaxby’s: Honey Mustard (1 portion cup)33010.00
[2018-06-23 Fri]Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal)230266.59
[2018-06-23 Fri]Arby’s: Sweet Tea (small)10000.00
[2018-06-23 Fri]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-23 Fri]Arby’s: Smoke Mountain sandwich820440.00
[2018-06-22 Thu]Campbell’s Chunky: Chicken Corn Chowder (1 can, 240ml)380101.79
[2018-06-22 Thu]Nature Valley: Dark Chocolate, Peanut & Almond granola bar16030.50
[2018-06-22 Thu]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-22 Thu]Chick-Fil-A: Classic Chicken Sandwich440283.09
[2018-06-22 Thu]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-06-22 Thu]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-06-22 Thu]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-06-22 Thu]Chick-Fil-A: Grilled Market Salad320267.25
[2018-06-21 Wed]Nature Valley: Protein Chewy Bar, peanut butter dark chocolate190100.70
[2018-06-21 Wed]Pepsi (~20 oz)25000.00
[2018-06-21 Wed]Campbell’s Chunky: Chili, Hot & Spicy with Bean Firehouse (1 can)480281.79
[2018-06-21 Wed]Chick-Fil-A: Sweet Tea (large)17000.00
[2018-06-21 Wed]Chick-Fil-A: Classic Chicken Sandwich440283.09
[2018-06-21 Wed]Chick-Fil-A: Waffle Potato Fries (medium)40051.69
[2018-06-20 Tue]Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice)360152.15
[2018-06-20 Tue]Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice)360152.15
[2018-06-20 Tue]Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice)360152.15
[2018-06-20 Tue]Zaxby’s: Sweet Tea (large)51000.50
[2018-06-20 Tue]Zaxby’s: Crinkle Fries (w/sandwich meal)37040
[2018-06-20 Tue]Zaxby’s: Chicken Finger Sandwich820426.99
[2018-06-19 Mon]Nature Valley: Protein Chewy Bar, peanut butter dark chocolate190100.70
[2018-06-19 Mon]Campbell’s Chunky: Chicken Corn Chowder (1 can, 240ml)380101.79
[2018-06-19 Mon]Chick-Fil-A: Chicken Salad Sandwich500274.09
[2018-06-19 Mon]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-06-19 Mon]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-06-19 Mon]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-06-19 Mon]Chick-Fil-A: Grilled Market Salad320267.25
[2018-06-19 Mon]Quaker: Steel Cut oatmeal w/blueberries, cranberries (1 packet)17040.34
[2018-06-18 Sun]Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice)360152.15
[2018-06-18 Sun]Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice)360152.15
[2018-06-18 Sun]Arby’s: Triple Chocolate Cookie45051.00
[2018-06-18 Sun]Arby’s: Sweet Tea (small)10000.00
[2018-06-18 Sun]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-18 Sun]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-18 Sun]Arby’s: Double Roast Beef sandwich510384.69
[2018-06-17 Sat]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-17 Sat]Nature Valley: Dark Chocolate, Peanut & Almond granola bar16030.50
[2018-06-17 Sat]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-17 Sat]Pizza Hut: Meaty Marinara Bake w/garlic bread (individual, 1/2)36016.50.00
[2018-06-17 Sat]Chick-Fil-A: Chick-n-Minis (4 count)350193.09
[2018-06-17 Sat]Chick-Fil-A: Hash Rounds (1 box)24021.19
[2018-06-17 Sat]Chick-Fil-A: Spicy Chicken Breakfast Burrito450303.90
[2018-06-17 Sat]Chick-Fil-A: Sweet Tea (large)17000.00
[2018-06-15 Thu]Nature Valley: Dark Chocolate, Peanut & Almond granola bar16030.50
[2018-06-15 Thu]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-15 Thu]Chick-Fil-A: Sweet Tea (large)17000.00
[2018-06-15 Thu]Chick-Fil-A: Chick-n-Minis (4 count)350193.09
[2018-06-15 Thu]Chick-Fil-A: Hash Rounds (1 box)24021.19
[2018-06-15 Thu]Chick-Fil-A: Spicy Chicken Breakfast Burrito450303.90
[2018-06-08 Thu]Chick-Fil-A: Fruit Cup (medium)4502.75
[2018-06-08 Thu]Chick-Fil-A: Grilled Chicken Cool Wrap350375.25
[2018-06-08 Thu]Chick-Fil-A: Dr. Pepper (medium)18001.59
[2018-06-08 Thu]Nature Valley: Protein Chewy Bar, peanut butter dark chocolate190100.70
[2018-06-08 Thu]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-07 Wed]Kellogg’s: Special K Protein Meal Bar, Chocolately Chip170121.00
[2018-06-07 Wed]Maruchan: Ramen noodles, beef (1 package with flavor packet)380100.21
[2018-06-07 Wed]Campbell’s: Slow Kettle Style Soup, SW-Style Chicken Chili (15.7 oz)380262.98
[2018-06-07 Wed]Chick-Fil-A: Grilled Market Salad320267.25
[2018-06-07 Wed]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-06-07 Wed]Chick-Fil-A: Nuggets (8 ct.)270283.09
[2018-06-07 Wed]Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice)305110.00
[2018-06-07 Wed]Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice)305110.00
[2018-06-06 Tue]Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice)305110.00
[2018-06-06 Tue]Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice)305110.00
[2018-06-06 Tue]Arby’s: Three-Cheese Steak sandwich650427.89
[2018-06-06 Tue]Arby’s: Mozzarella Sticks (4 piece)440191.30
[2018-06-06 Tue]Arby’s: Mt. Dew (small)20000.00
[2018-06-05 Mon]Arby’s: Mozzarella Sticks (4 piece)440192.99
[2018-06-05 Mon]Arby’s: Double Roast Beef sandwich510384.69
[2018-06-05 Mon]Sonic: Sweet Tea (Rt. 44)36000.00
[2018-06-04 Sun]Arby’s: Triple Chocolate Cookie45051.00
[2018-06-04 Sun]Papa John’s: Bacon Cheddarburger pan pizza, 1 slice330120.00
[2018-06-04 Sun]Papa John’s: Bacon Cheddarburger pan pizza, 1 slice330120.00
[2018-06-04 Sun]Papa John’s: Bacon Cheddarburger pan pizza, 1 slice330120.00
[2018-06-04 Sun]Arby’s: Salted Caramel & Chocolate Cookie43041.00
[2018-06-04 Sun]Arby’s: Dr. Pepper (small)13001.59
[2018-06-04 Sun]Arby’s: Half-Pound Roast Beef sandwich610485.69
[2018-06-04 Sun]Arby’s: Mozzarella Sticks (4 piece)440192.99
[2018-06-03 Sat]Sonic: Custard Concrete, Dark Chocolate & Oreo (2/3 of small)59095.47
[2018-06-03 Sat]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-03 Sat]Dickey’s: green beans w/bacon (4 oz. side)8432.25
[2018-06-03 Sat]Dickey’s: waffle fries (4 oz. side)33842.25
[2018-06-03 Sat]Dickey’s: Westerner w/chopped brisket, pulled pork (1 sandwich)86843.58.50
[2018-06-02 Fri]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.42
[2018-06-02 Fri]Papa John’s: Bacon Cheddarburger pan pizza, 1 slice330120.00
[2018-06-02 Fri]Papa John’s: Bacon Cheddarburger pan pizza, 1 slice330120.00
[2018-06-02 Fri]Arby’s: Mozzarella Sticks (4 piece)440192.99
[2018-06-02 Fri]Arby’s: Double Roast Beef sandwich510384.69
[2018-06-01 Thu]Pepperidge Farm: Swirl caramel apple bread (1 slice)10030.29
[2018-06-01 Thu]Pepperidge Farm: Swirl caramel apple bread (1 slice)10030.29
[2018-06-01 Thu]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.75
[2018-06-01 Thu]KFC: Chicken Pot Pie790294.99
[2018-06-01 Thu]Chick-Fil-A: Sweet Tea (large)17000.00
[2018-06-01 Thu]Chick-Fil-A: Chick-n-Minis (4 count)350193.09
[2018-06-01 Thu]Chick-Fil-A: Hash Rounds (1 box)24021.19
[2018-06-01 Thu]Chick-Fil-A: Spicy Chicken Breakfast Burrito450303.90
[2018-04-27 Thu]Sonic: Dr. Pepper w/cranberry, vanilla (large)32501.75
[2018-04-27 Thu]Chick-Fil-A: Grilled Market Salad320267.25
[2018-04-27 Thu]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-04-27 Thu]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-04-27 Thu]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-04-18 Tue]Sonic: Chili Cheese Coney (regular)500200.00
[2018-04-18 Tue]Sonic: Tater Tots (small)25020.00
[2018-04-18 Tue]Chick-Fil-A: Grilled Market Salad320267.25
[2018-04-18 Tue]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-04-18 Tue]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-04-18 Tue]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-04-18 Tue]Chick-Fil-A: Spicy Chicken Sandwich490303.35
[2018-04-18 Tue]Sonic: Dr. Pepper w/cranberry, vanilla (large)32502.89
[2018-04-07 Fri]Chick-Fil-A: Hash Rounds (1 box)24021.19
[2018-04-07 Fri]Chick-Fil-A: Fruit Cup (small)452.092.09
[2018-04-07 Fri]Chick-Fil-A: Spicy Chicken Breakfast Burrito450303.90
[2018-03-28 Tue]Chick-Fil-A: Grilled Market Salad320267.19
[2018-03-28 Tue]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-03-28 Tue]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-03-28 Tue]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
[2018-03-28 Tue]Chick-Fil-A: Spicy Chicken Sandwich490303.35
[2018-03-27 Mon]Chick-Fil-A: Chicken Soup (small)140122.69
[2018-03-27 Mon]Nature’s Own: whole wheat hot dog bun11050.40
[2018-03-27 Mon]McCormick: Grill Mates, Montreal Steak smoked sausage (1 link)18090.37
[2018-03-27 Mon]Nature’s Own: whole wheat hot dog bun11050.40
[2018-03-27 Mon]Ballpark: Angus Beef Frank (original)15060.62
[2018-03-27 Mon]Chick-Fil-A: Grilled Market Salad320267.19
[2018-03-27 Mon]Chick-Fil-A: Harvest Nut Granola (1 packet)6010.00
[2018-03-27 Mon]Chick-Fil-A: Roasted Nut Blend (1 packet)7010.00
[2018-03-27 Mon]Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet)20000.00
<57>

Calorie goal data

[2018-06-27 Tue 04:21] Had it on 1910 forever. Haven’t been doing YAYOG lately though, so with a “sedentary” activity multiplier of 1.2, it’s 1836. Let’s try that for now.

!DateGoal
[2018-06-27 Tue]1836
[2017-09-23 Fri]1910
[2017-08-23 Tue]1910

Training

Training data

!DateMovementRepsTypeComments
[2018-07-19 Mon]Let Me Ins (underhand grip, knees slightly bent)24Supersets/2nd
[2018-07-19 Mon]Let Me Ups (knees bent 90 degrees)10Supersets/1st
[2018-07-19 Mon]Let Me Ups (knees bent 90 degrees)6Supersets/2nd
[2018-07-19 Mon]Let Me Ins (4-second hold; knees bent 90 degrees)10Supersets/1st
[2018-07-19 Mon]Let Me Ins (knees bent 90 degrees)24Supersets/2nd
[2018-07-19 Mon]Pull-Ups (assisted)10Supersets/1st
[2018-07-17 Sat]Good Mornings43Ladders
[2018-07-17 Sat]Squats (2-second pause at bottom)48Ladders
[2018-07-17 Sat]One-legged Romanian Deadlifts (2-second pause)21Ladders
[2018-07-17 Sat]Back Lunges31Ladders
[2018-07-15 Thu]Burpees (w/hip-height push-ups)32Tabatas
[2018-07-15 Thu]Rocking Chairs (arms extended)48Tabatas
[2018-07-15 Thu]Classic Push-Ups (hands hip height)56Tabatas
[2018-07-13 Tue]Classic Push-Ups (hands hip height)36Stappers
[2018-07-13 Tue]Let Me Ins (knees bent 90 degrees)48Stappers
[2018-07-13 Tue]Back Lunges70Stappers
[2018-07-12 Mon]Standing Knee Raises64Tabatas
[2018-07-12 Mon]Beach Scissors (lying on side)48Tabatas
[2018-07-12 Mon]Russian Twists88Tabatas
[2018-07-10 Sat]Towel Curls36Intervals
[2018-07-10 Sat]Let Me Ins (underhand grip, knees slightly bent)36Intervals
[2018-07-10 Sat]Let Me Ups (knees bent 90 degrees)17Intervals
[2018-07-10 Sat]Let Me Ins (knees bent 90 degrees)36Intervals
[2018-07-09 Fri]Calf Raises (both legs)24Supersets/2nd
[2018-07-09 Fri]One-legged Romanian Deadlifts (2-second pause)10Supersets/1st
[2018-07-09 Fri]Side Lunges24Supersets/2nd
[2018-07-09 Fri]Lunges (4-second pause)10Supersets/1st
[2018-07-09 Fri]Toyotas18Supersets/2nd
[2018-07-09 Fri]Back Lunges (hands behind head, 4-second pause)10Supersets/1st
[2018-07-08 Thu]Seated Dips (knees bent)46Ladders
[2018-07-08 Thu]Close-Grip Push-Ups (hands hip height)48Ladders
[2018-07-08 Thu]Classic Push-Ups (hands knee height)18Ladders
[2018-07-08 Thu]Military Press (hands knee height)40Ladders
[2018-07-06 Tue]Leg Lifts (hands under butt)24Supersets/2nd
[2018-07-06 Tue]Supermans10Supersets/1st
[2018-07-06 Tue]Swimmers (slow)24Supersets/2nd
[2018-07-06 Tue]Hyperextensions (hands at side)10Supersets/1st
[2018-07-06 Tue]Russian Twists24Supersets/2nd
[2018-07-06 Tue]V-Ups10Supersets/1st
[2018-07-05 Mon]Let Me Ins (underhand grip, knees slightly bent)20Supersets/2nd
[2018-07-05 Mon]Let Me Ups (knees bent 90 degrees)5Supersets/1st
[2018-07-05 Mon]Towel Curls24Supersets/2nd
[2018-07-05 Mon]Let Me Ins (4-second hold; knees bent 90 degrees)10Supersets/1st
[2018-07-05 Mon]Let Me Ins (knees bent 90 degrees)24Supersets/2nd
[2018-07-05 Mon]Pull-Ups (assisted)10Supersets/1st
[2018-07-02 Fri]Squats (2-second pause at bottom)24Supersets/2nd
[2018-07-02 Fri]One-legged Romanian Deadlifts10Supersets/1st
[2018-07-02 Fri]Side Lunges24Supersets/2nd
[2018-07-02 Fri]Lunges (4-second pause)8Supersets/1st
[2018-07-02 Fri]Toyotas22Supersets/2nd
[2018-07-02 Fri]Back Lunges (hands behind head, 4-second pause)10Supersets/1st
[2018-06-30 Wed]Close-Grip Push-Ups (hands hip height)10Supersets/1st
[2018-06-30 Wed]Thumbs Up24Supersets/2nd
[2018-06-30 Wed]Military Press (hands knee height)10Supersets/1st
[2018-06-30 Wed]Shove Offs (hands hip height)24Supersets/2nd
[2018-06-30 Wed]Classic Push-Ups (hands on ground)5Supersets/1st
[2018-06-29 Tue]Leg Lifts (hands under butt)24Supersets/2nd
[2018-06-29 Tue]Supermans10Supersets/1st
[2018-06-29 Tue]Swimmers (slow)24Supersets/2nd
[2018-06-29 Tue]Hyperextensions (hands at side)10Supersets/1st
[2018-06-29 Tue]Russian Twists24Supersets/2nd
[2018-06-29 Tue]V-Ups10Supersets/1st
[2018-06-22 Tue]Let Me Ins (underhand grip, knees slightly bent)20Supersets/2nd
[2018-06-22 Tue]Let Me Ups (knees bent 90 degrees)4Supersets/1st
[2018-06-22 Tue]Towel Curls24Supersets/2nd
[2018-06-22 Tue]Let Me Ins (4-second hold; knees bent 90 degrees)10Supersets/1st
[2018-06-22 Tue]Let Me Ins (knees bent 90 degrees)24Supersets/2nd
[2018-06-22 Tue]Pull-Ups (assisted)6Supersets/1st
[2018-06-18 Fri]Squats (2-second pause at bottom)24Supersets/2nd
[2018-06-18 Fri]One-legged Romanian Deadlifts10Supersets/1st
[2018-06-18 Fri]Side Lunges24Supersets/2nd
[2018-06-18 Fri]Lunges10Supersets/1st
[2018-06-18 Fri]Toyotas18Supersets/2nd
[2018-06-18 Fri]Back Lunges10Supersets/1st
[2018-06-17 Thu]Seated Dips (knees bent)24Supersets/2nd
[2018-06-17 Thu]Close-Grip Push-Ups (hands hip height)10Supersets/1st
[2018-06-17 Thu]Thumbs Up24Supersets/2nd
[2018-06-17 Thu]Military Press (hands knee height)9Supersets/1st
[2018-06-17 Thu]Shove Offs (hands hip height)24Supersets/2nd
[2018-06-17 Thu]Classic Push-Ups (hands on ground)5Supersets/1st
[2018-06-16 Wed]Swimmers36Intervals
[2018-06-16 Wed]Russian Twists36Intervals
[2018-06-16 Wed]Hyperextensions (hands under chin)36Intervals
[2018-06-16 Wed]Leg Lifts (hands under butt)36Intervals
[2018-06-12 Sat]Towel Curls12Intervals
[2018-06-12 Sat]Let Me Ins (underhand grip, knees slightly bent)36Intervals
[2018-06-12 Sat]Let Me Ups (knees bent 90 degrees)16Intervals
[2018-06-12 Sat]Let Me Ins (knees bent 90 degrees)36Intervals
[2018-06-10 Thu]One-legged Romanian Deadlifts36Intervals
[2018-06-10 Thu]Squats (2-second pause at bottom)36Intervals
[2018-06-10 Thu]Side Lunges36Intervals
[2018-06-10 Thu]Bulgarian Split Squats (hands behind head)22Intervals
[2018-06-07 Mon]Classic Push-Ups (hands hip height)36Intervals
[2018-06-07 Mon]Military Press (hands knee height)12Intervals
[2018-06-07 Mon]Close-Grip Push-Ups (hands hip height)36Intervals
[2018-06-07 Mon]Seated Dips (legs straight)6Intervals
[2018-06-06 Sun]Swimmers36Intervals
[2018-06-06 Sun]Russian Twists36Intervals
[2018-06-06 Sun]Hyperextensions (hands under chin)36Intervals
[2018-06-06 Sun]Leg Lifts (hands under butt)36Intervals
[2018-06-03 Thu]Towel Curls25Intervals
[2018-06-03 Thu]Let Me Ins (underhand grip, knees slightly bent)31Intervals
[2018-06-03 Thu]Let Me Ups (knees bent 90 degrees)13Intervals
[2018-06-03 Thu]Let Me Ins (knees bent 90 degrees)36Intervals
[2018-06-01 Tue]One-legged Romanian Deadlifts34Intervals
[2018-06-01 Tue]Squats (2-second pause at bottom)36Intervals
[2018-06-01 Tue]Side Lunges36Intervals
[2018-06-01 Tue]Bulgarian Split Squats (hands behind head)32Intervals
[2018-05-28 Fri]Classic Push-Ups (hands hip height)36Intervals
[2018-05-28 Fri]Military Press (hands knee height)20Intervals
[2018-05-28 Fri]Close-Grip Push-Ups (hands hip height)14Intervals
[2018-05-28 Fri]Seated Dips (knees bent)36Intervals
[2018-05-27 Thu]Russian Twists93Ladders
[2018-05-27 Thu]Squats (2-second pause at bottom)55Ladders
[2018-05-27 Thu]One-legged Romanian Deadlifts36Ladders
[2018-05-27 Thu]Side Lunges40Ladders
[2018-05-26 Wed]Let Me Ups (knees bent 90 degrees)40Ladders
[2018-05-26 Wed]Seated Dips (knees bent)48Ladders
[2018-05-26 Wed]Let Me Ins (knees bent 90 degrees)43Ladders
[2018-05-26 Wed]“Let Me Downs (push-ups, hands knee height)”21Ladders
[2018-05-24 Mon]Swimmers51Ladders
[2018-05-24 Mon]Squats58Ladders
[2018-05-24 Mon]One-legged Romanian Deadlifts33Ladders
[2018-05-24 Mon]Back Lunges28Ladders
[2018-05-21 Fri]Let Me Ups (knees bent 90 degrees)33Ladders
[2018-05-21 Fri]Seated Dips (knees bent)43Ladders
[2018-05-21 Fri]Let Me Ins (knees bent 90 degrees)43Ladders
[2018-05-21 Fri]“Let Me Downs (push-ups, hands knee height)”18Ladders
[2018-05-19 Wed]Russian Twists85Ladders
[2018-05-19 Wed]Squats (2-second pause at bottom)46Ladders
[2018-05-19 Wed]One-legged Romanian Deadlifts30Ladders
[2018-05-19 Wed]Side Lunges36Ladders
[2018-05-10 Mon]“Let Me Downs (push-ups, hands knee height)”15Ladders
[2018-05-10 Mon]Let Me Ins (knees bent 90 degrees)33Ladders
[2018-05-10 Mon]Seated Dips (knees bent)40Ladders
[2018-05-10 Mon]Let Me Ups (knees bent 90 degrees)25Ladders
[2018-05-04 Tue]Back Lunges25Ladders
[2018-05-04 Tue]One-legged Romanian Deadlifts28Ladders
[2018-05-04 Tue]Squats40Ladders
[2018-05-04 Tue]Swimmers46Ladders

Calories-burned data

!DateBurnedComment
[2018-07-01 Sun]171W3D2
[2018-06-27 Wed]171W2D4
[2018-06-25 Mon]171W2D3
[2018-06-24 Sun]171W2D2
[2018-06-21 Thu]171W2D1
[2018-06-19 Tue]171W1D4
[2018-06-16 Sat]109
[2018-06-10 Sun]389
[2018-06-03 Sun]389
[2018-05-28 Mon]263

Weight

!DateWeightComment
[2018-03-27 Mon 16:24]172.2
[2018-03-27 Mon 22:32]174
[2018-06-02 Fri 12:53]177.8
[2018-06-03 Sat 14:45]179.6
[2018-06-06 Tue 17:45]179.6
[2018-06-07 Wed 15:04]177.6
[2018-06-17 Sat 09:19]178.4
[2018-06-21 Wed 15:29]177.2
[2018-06-22 Thu 15:58]178.2
[2018-06-24 Sat 16:30]177.2
[2018-06-27 Tue 19:18]178.2
[2018-07-04 Tue 10:09]178.2
[2018-07-07 Fri 13:26]176
[2018-07-07 Fri 15:38]178.2
[2018-07-10 Mon 15:39]178.2
[2018-07-11 Tue 15:49]176.8
[2018-07-13 Thu 08:07]177.8
[2018-07-15 Sat 18:46]176
[2018-07-15 Sat 19:39]178.2
[2018-07-20 Thu 05:39]177.4
[2018-07-21 Fri 09:51]178.0
[2018-07-28 Fri 15:16]178.0
[2018-07-29 Sat 17:27]176.8

Reports

Cost totals

Optimal days

Food sorted

By price

(org-fitness-list-food-by "price")

By calories

(org-fitness-list-food-by "calories")

By protein

By calories/protein

FoodCalories/ProteinCaloriesProteinPriceComment
Chicken-of-the-Sea: Canned Salmon (1 5-oz can)4120261.00
Met-Rx: 100% Natural Whey Chocolate (1 scoop)5130230.93
Kroger: canned pulled pork (56g)560120.76
Kroger: canned pulled pork (1/2 of 10 oz. can)5105211.33
Kroger: Roast Beef, deli sliced (2 oz)560110.48
Kroger: turkey breast, honey smoked, deli thin sliced (2 oz)66090.66
Kroger: turkey breast, mesquite smoked, deli thin sliced66090.66
Kroger: deli fried chicken tender (1 piece, ~125g)830837.51.66
Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal)8230266.59Price of Snak Meal
Chick-Fil-A: Nuggets (8 ct.)9270283.09
Chick-Fil-A: Chick-n-Strips (3 count)10360333.39
Chick-Fil-A: Chick-n-Strips (4 count)10470434.39
Chick-Fil-A: Chicken Soup (small)11140122.69
Chick-Fil-A: Chicken Tortilla Soup11260223.65
Chick-Fil-A: Grilled Market Salad12320267.19
Kroger: swiss cheese (1 slice)138060.31
homemade: hamburger patty (100g, guessing)13204150.00from visiting Ms. Dodie
Arby’s: Roast Beef Mid sandwich13460334.29
Kraft: Philadelphia cream cheese, black cherry (1 tbsp)14352.50.22
Jimmy Dean: Bacon Breakfast Bowl14410282.49
Kraft: Philadelphia cream cheese, black cherry (2 tbsp)147050.43
Arby’s: Angus Three Cheese & Bacon sandwich14670476.00Meal was actually free since they got yesterday’s wrong.
Wendy’s: Baconator (no ketchup)15940598.59
Arby’s: Three Cheese Steak sandwich w/bacon (1/2 of it)15368246.60Counting full price of it.
KFC: Nashville Hot Chicken (3 tenders)15530345.49Nutrition info from a non-KFC site; price includes sides
Arby’s: Three Cheese Steak sandwich w/bacon15736486.60
Chick-Fil-A: Classic Chicken Sandwich15440283.09
Arby’s: French Dip & Swiss sandwich15540356.74
Kroger: deli fried chicken tender (1 piece, ~102g)16304181.35
Chick-Fil-A: Spicy Chicken Sandwich16490303.35
Kroger: cheddar cheese (1 slice)168050.20From 4-type multi pack, cheaper than cheddar-only package
Kroger: Monterey Jack cheese (1 slice)168050.20
Kroger: deli fried chicken tender (1 piece, 67g)16200120Check price
Kroger: deli fried chicken tender (1 piece, ~107g)16319191.43
Kroger: Colby cheese (1 slice)168050.20
Hardee’s: Bacon 3-Way, 1/2 lb. (w/o cheese, lettuce, tomato)1710205710.283 am election celebration (didn’t give me receipt)
Campbell’s Chunky: Chili, Hot & Spicy with Bean Firehouse (1 can)17480281.79
Chick-Fil-A: Chicken, Egg & Cheese Bagel (1 sandwich)17480273.79
Thomas: 100% Whole Wheat Bagel Thins (1 bagel thin)1811060.00Free from Ms. Dodie (someone gave them to her)
Campbell’s Chunky Soup: Spicy BBQ Seasoned Chicken w/Beans (1 can)19420221.79
Chick-Fil-A: Greek yogurt parfait w/chocolate cookie crumbs19210112.95
McCormick: Grill Mates, Montreal Steak smoked sausage (1 link)2018090.37
Jimmy Dean: Turkey Sausage & Bacon frittatas (2)20260131.96
Chick-Fil-A: Spicy Chicken, Egg, and Cheese Biscuit20560283.19
Campbell’s Chunky: Savory Pot Roast20240121.79
Chick-Fil-A: Chicken, Egg, and Cheese Biscuit20560283.98
Nature’s Own: whole wheat hamburger bun2113060.40
Taco Bell: Steakhouse Burrito, steak (1/2 burrito)2140019.56.19Ate half, counted full price
Hardee’s: Pork Chop’n’Gravy Biscuit21590285.46Combo with small drink, small hash browns
Taco Bell: Cruncy Taco (beef, cheese, lettuce)2117080.00Price included in combo
Wolf Brand: chili, no beans (1 can)21800382.19
Nature’s Own: whole wheat hot dog bun2211050.40
Banquet: Mega Meal, salisbury steak22640281.99
Taco Bell: DoubleDilla, steak, chips & salsa22920415.95They ran it up COMPLETELY wrong, so I’ve no idea how much it would actually cost.
Campbell’s Chunky: Chili Mac (1 can)22400181.79
Nature’s Own: honey wheat bread (2 slices)2314060.32
Arby’s: Mozzarella Sticks (3 piece)23345151.38He gave me a 7-piece by mistake, so I ate 4 and now the rest.
Arby’s: Mozzarella Sticks (4 piece)23460201.84
Stouffer’s: Chicken Fettucini Alfredo24540222.79
Hardee’s: Country Ham Biscuit24370150.00
Quaker: Protein Oatmeal, Cranberry Almond (1 packet)24240100.50
KFC: Chicken Little, Nashville Hot24320131.59Don’t know if Nashville Hot makes a difference
Tai Pei: Pepper Beef (1 package, 403g)25490192.50
Campbell’s Chunky: Chipotle Chicken & Corn Chowder (1 can, 240ml)25360141.79
Kroger: creamy peanut butter (2 tbsp/32 g)2518070.12
Kroger: creamy peanut butter (1 tbsp/16 g)26903.50.06
Pepperidge Farm: Swirl cinnamon raisin bread (1 slice)268030.19
Campbell’s Chunky Soup: Chicken, Broccoli, Cheese w/potato (1 can)27380141.79
KFC: Chicken Pot Pie27790295.20Price included cookie and drink, plus 0.20 for large size drink. Was up all night, ate this at like 5am.
Chick-Fil-A: Spicy Chicken Biscuit2744016Free one from last time, but they also left off the egg and cheese this time.
Taco Bell: Quesarito, beef, (- sour cream, nacho cheese)28590215.95
Taco Bell: Beefy Fritos Burrito (-nacho cheese, w/3 cheese blend)28450161.39
Wal-Mart/Marketside: chicken salad w/cranberries, pecans (1/4 cup)3015050.00Check price
Thomas: blueberry bagel3027090.67
Quaker: Old Fashioned Oatmeal (1/2 cup)3015050.00
Oroweat: sesame-seeded sandwich roll3015050.00from visiting Ms. Dodie
Glutenfreeda: Banana Maple with Flax instant oatmeal3018060.00Check price
Thomas: “Everything” bagel3128090.58
Taco Bell: Steakhouse Queso Nachos, steak (see comments)32880276.19No sour cream, no beans, nacho cheese sauce instead of whatever it was supposed to have (they appear to have not made it right, big surprise).
Kroger: bagel, cinnamon raisin (1 bagel)3226080.33
Pepperidge Farm: Swirl caramel apple bread (1 slice)3310030.29
Sara Lee: Sweet Hawaiian Sandwich Roll (1 bun)3417050.34
Kroger: Fresh Foods Market: loaded baked potato style soup (15 oz)35640183.59
Tai Pei: Sweet & Sour Chicken (1 container, 397g)35460132.50Blech. Hardly any chicken. Not doing this again.
Maruchan: Ramen noodles, beef (1 package with flavor packet)38380100.21
Campbell’s Chunky: Chicken Corn Chowder (1 can, 240ml)38380101.79
Kellogg’s: Special K Nourish Apple Raspberry Almond cereal (1 cup)3819050.64
Quaker: Instant Oatmeal, apple cinnamon (1 packet)4016040.00
Quaker: Steel Cut oatmeal w/blueberries, cranberries (1 packet)4217040.34Price from Google search, Walmart listing
KFC: biscuit4518040.00
Chick-Fil-A: Peppermint Chocolate Chip milkshake (1/2 of small)4929561.43
Marie Callender’s: Turkey Pot Pie (1 pie, 16 oz)491090222.5
Zaxby’s: Texas Toast (1 piece)5015030.00Price included in Snak Meal
Chick-Fil-A: Harvest Nut Granola (1 packet)606010.00
Taco Bell: Chips and Salsa6225041.29
Wendy’s: Fries (small)6432050.00
KFC: Potato Wedges (individual size)6727040.00
Chick-Fil-A: Seasoned Tortilla Strips707010.00
Chick-Fil-A: Roasted Nut Blend (1 packet)707010.00
blueberries (1/4 cup)7621.250.280.00Check price
Kraft: Philadelphia cream cheese, pumpkin spice (1 tbsp)80400.50.21
Chick-Fil-A: Waffle Potato Fries (medium)8040051.69
Kraft: Philadelphia cream cheese, strawberry (1 tbsp)80400.50.21
Chick-Fil-A: Chocolate Chip Cookie8735041.19
Zaxby’s: Crinkle Fries (5 oz)8844050.00Price included in Snak Meal
Arby’s: Triple Chocolate Cookie9045051.00
Zaxby’s: Brownie9036040.99
Hardee’s: Natural-Cut French Fries (small)903604
Arby’s: Apple Crisp9557062.49
homemade gingerbread cookie10010010.00Dr. Oden brought cookies to share today
Banana (medium)10510510.22
Arby’s: Salted Caramel & Chocolate Cookie10743041.00
Chick-Fil-A: Hash Rounds (1 box)12024021.19
Hardee’s: Hash Rounds (small)13026020.00Price included in combo
Honeycrisp apple (208g)2161080.50.00Check price
Zaxby’s: Honey Mustard (1 portion cup)33033010.00

Notes

[2018-03-27 Mon 23:06] Blank line in calorie goal data broke script with weird errors

[2018-03-27 Mon 23:06] Somehow a blank line got added to the bottom of the calorie goal data table, and it caused the Python script to fail when converting the dates in the other tables. The error was on the weight data, saying that a date value was empty, but it was definitely not empty, and I verified it by both printing the data and by checking in the REPL with the script loaded into it. The line that gave the error even worked when I ran it directly in the REPL.

I had the idea to check the file with git to see what changes had been made, so I used Magit to view the changes made to this file, and sure enough, 7 hours ago there was a commit with a blank line added to the calorie goal data. It must have happened when I logged my weight for the first time in a long time. I need to look at the code to see what I did wrong, but at least I can fix it temporarily.

Code

IN-USE Modularized plotting

[2016-10-20 Thu 21:37] This works but the smoothing is all jagged compared to the non-workout-plotting version. I don’t know why, but adding the workouts frame to it makes the rolling all jagged, even though they are plotted separately. :(

[2016-10-28 Fri 00:08] When I merge the workout data in, the other data becomes jagged. My best guess is that it prevents the resampling or smoothing from working the same way by each day having multiple workout rows preventing the date index from being collapsed into one row per day. If so, maybe I can merge and plot in a different order, or just do them separately.

[2016-10-28 Fri 20:16] Switching to it now!

[2016-12-03 Sat 02:28] Added Hodrick-Prescott filters from python-statsmodels, which removes cyclical features from the data and shows long-term trends. Amazing!

[2016-12-15 Thu 13:48] I think if I move this script to a Python module, I should be able to import it and call it from here, which would allow me to keep the Python script in a separate git repo, which would be nice for development.

[2017-06-26 Mon 19:46] Modularized using Org-babel and noweb syntax. Seems to work, both the HP and non-HP versions. This should be easier to experiment with. Copied log entries above from old code sections, which I’m removing now.

IN-USE Imports

import itertools, os, re, sys

from datetime import datetime, timedelta

import matplotlib.pyplot as plot
import matplotlib.lines as mlines

from matplotlib.dates import DateFormatter, YearLocator, MonthLocator, WeekdayLocator, num2date, SA, SU, date2num
from matplotlib.font_manager import FontProperties
from matplotlib.colors import ColorConverter

import numpy, pandas, random

IN-USE Constants

WEIGHT_DATE_FORMAT = "[%Y-%m-%d %a %H:%M]"
PANDAS_DATE_FORMAT = "%Y-%m-%d %a %H:%M"
CALORIE_DATE_FORMAT = "[%Y-%m-%d %a]"
FOOD_DATE_FORMAT = "[%Y-%m-%d %a]"

DPI = 100
WIDTH = round(float(WINDOW_WIDTH) / 100, 1)
HEIGHT = 5.55

# FIXME: Commenting out because data isn't loaded yet so if it's "all" it doesn't work.

# OLDEST_DATE = datetime.strptime(data[1][1], FOOD_DATE_FORMAT) \
#               if DAYS == "all" \
#                  else datetime.now() - timedelta(days=DAYS)

HALF_WINDOW = "%s%s" % (int(float(re.search("[0-9]+", WINDOW).group(0)) / 2),
                        re.search("[a-z]+", WINDOW).group(0))

# Series type marker types
SERIES_MARKERS = {"Ladders": "D",
                  "Intervals": ".",
                  "Supersets/1st": "v",
                  "Supersets/2nd": "^",
                  "Tabatas": "+",
                  "Stappers": "s"}

# Solarized colors
yellow = '#b58900'
green = '#b58900'
orange = '#cb4b16'
red = '#dc322f'
magenta = '#d33682'
violet = '#6c71c4'
blue = '#268bd2'
cyan = '#2aa198'
green = '#859900'
base03 = '#002b36'
base02 = '#073642'
base01 = '#586e75'
base00 = '#657b83'
base0 = '#839496'
base1 = '#93a1a1'
base2 = '#eee8d5'
base3 = '#fdf6e3'

border_color = "#0e2329"
dark_bg = "#0e2329"

class SolarizedColors (object):
    yellow   = '#b58900'
    orange   = '#cb4b16'
    red      = '#dc322f'
    magenta  = '#d33682'
    violet   = '#6c71c4'
    blue     = '#268bd2'
    cyan     = '#2aa198'
    green    = '#859900'

    base0 = '#839496'
    base00 = '#657b83'
    base1 = '#93a1a1'
    base01 = '#586e75'
    base2 = '#eee8d5'
    base02 = '#073642'
    base3 = '#fdf6e3'
    base03 = '#002b36'

    yellow_hc = '#DEB542'
    yellow_lc = '#7B6000'
    orange_hc = '#F2804F'
    orange_lc = '#8B2C02'
    red_hc = '#FF6E64'
    red_lc = '#990A1B'
    magenta_hc = '#F771AC'
    magenta_lc = '#93115C'
    violet_hc = '#9EA0E5'
    violet_lc = '#3F4D91'
    blue_hc  = '#69B7F0'
    blue_lc  = '#00629D'
    cyan_hc  = '#69CABF'
    cyan_lc  = '#00736F'
    green_hc = '#B4C342'
    green_lc = '#546E00'

c = SolarizedColors
solarized_colors = [c.yellow, c.orange, c.red, c.magenta, c.blue, c.violet, c.cyan, c.green,
                    c.yellow_hc, c.orange_hc, c.red_hc, c.magenta_hc, c.blue_hc, c.violet_hc, c.cyan_hc, c.green_hc,
                    c.yellow_lc, c.orange_lc, c.red_lc, c.magenta_lc, c.blue_lc, c.violet_lc, c.cyan_lc, c.green_lc]

# Expand color list
# from grapefruit import Color
# solarized_colors.extend([str(Color.NewFromHtml(c).Desaturate(0.25).html)
#                          for c in solarized_colors])

def printerr(*args, **kwargs):
    sys.stderr.write("\nSTDERR:\n" + ' '.join([str(a) for a in args]) + "\n")
    if 'exit' in kwargs and kwargs['exit']:
        sys.exit(1)

IN-USE Functions

def time_to_noon(d):
    """Return date D with hour set to 12."""

    return d.replace(hour=12)

def printerr(*args, **kwargs):
    sys.stderr.write("\nSTDERR:\n" + ' '.join([str(a) for a in args]) + "\n")
    if 'exit' in kwargs and kwargs['exit']:
        sys.exit(1)

def plot_hp(series, color='', label='', ax=None, secondary_y=False):
    """Plot series using Hodrick-Prescott filter."""

    series_fixed = series.ffill().dropna()
    cycle, trend = sa.tsa.filters.hpfilter(series_fixed)
    trend.plot(color=color, label=label, ax=ax, secondary_y=secondary_y)

IN-USE Process data

# Load into frames

# Drop last row of food data, containing column-size settings
frames = {'food': pandas.DataFrame.from_records(food_data[1:-1],
                                                columns=food_data[0]),
          'weight': pandas.DataFrame.from_records(weight_data[1:], columns=weight_data[0]),
          'goals': pandas.DataFrame.from_records(calorie_goal_data[1:], columns=calorie_goal_data[0]),
          'burned': pandas.DataFrame.from_records(calories_burned_data[1:], columns=calories_burned_data[0]),
          'workouts': pandas.DataFrame.from_records(workout_data[1:], columns=workout_data[0])}

# Drop food data without a date

# [2017-06-26 Mon 18:58] This should work fine. And it does...except
# that it triggers an inexplicable bug in Pandas that, after
# resampling the dataframe, makes the Calories column disappear.
# Printing the dataframe and describing it before and after this
# query() shows that the only thing that changes is that one row is
# removed. But I guess something changes internally that triggers the
# bug later. Why it picks ONLY the one column to drop, which isn't
# even one that the query touches, is inexplicable. Perhaps related
# is another buggy observation, that after the 0-to-NaN conversion,
# before the resampling, describe() on the dataframe shows only the
# two columns that were converted, as if the rest of the columns were
# dropped. Yet at the same time, printing the whole dataframe shows
# all of the columns. And then resampling drops the Calories column.

# So the end result is that I cannot use query() to remove a row
# containing the Org-mode column-size setting (e.g. "<57>"), so I
# can't use column-size settings in the table.

# This seems like the kind of bug that should be reported to Pandas.
# But I don't want to post all of my food and weight and workout data
# on a bug tracker, and trying to reduce this to a minimal testcase
# does not seem rewarding.

#frames['food'] = frames['food'].query('Date != ""')

# Convert date fields to datetime objects
for frame in ['food', 'goals', 'burned', 'workouts']:
    frames[frame]['Date'] = frames[frame]['Date'].apply(pandas.to_datetime, format=FOOD_DATE_FORMAT)
    frames['weight']['Date'] = frames['weight']['Date'].apply(pandas.to_datetime, format=WEIGHT_DATE_FORMAT)

# Drop old data
# for frame in frames:
# frames[frame] = frames[frame][frames[frame]['Date'] > OLDEST_DATE]

# Set date as index
for frame in frames:
    frames[frame] = frames[frame].set_index(['Date'])

# Convert 0 values to NaN (otherwise the whole column disappears, who knows why)
for col in ['Protein', 'Price']:
    frames['food'][col] = frames['food'][col].apply(lambda p: numpy.nan if not p else p)

# Resample food frame, summing values for each day
frames['food'] = frames['food'].resample('1d').sum()

# Merge frames together
combined_frame = frames['food'].merge(frames['weight'], how='outer', left_index=True, right_index=True)
combined_frame = combined_frame.merge(frames['burned'], how='outer', left_index=True, right_index=True)
combined_frame = combined_frame.merge(frames['goals'], how='outer', left_index=True, right_index=True)

# Fill empty goal values
combined_frame.Goal = combined_frame.Goal.ffill()

# FIXME: Calculate oldest date here after the data is loaded.
# Use -2 to get the actual last row of data, because -1 would be the row that sets column width, I think.
OLDEST_DATE = datetime.strptime(food_data[-2][1], FOOD_DATE_FORMAT) \
             if DAYS == "all" \
                else datetime.now() - timedelta(days=DAYS)

# Drop old data
combined_frame = combined_frame[combined_frame.index > OLDEST_DATE]
# Not strictly necessary to also drop workouts data, because we reset
# the ticks and axis limits after plotting the combined_frame. But
# might as well do it for consistency and speed.
frames['workouts'] = frames['workouts'][frames['workouts'].index > OLDEST_DATE]

IN-USE Setup figure

# Set params
#plot.rcParams['font.family'] = 'DejaVu Sans'  # It already gets DejaVu Sans as my default, but in case I ever want to change it...

# Setup figure (before plotting)
fig, axes = plot.subplots(nrows=2, ncols=1, facecolor=c.base03, dpi=DPI, figsize=(WIDTH, HEIGHT), sharex=True)
workouts_plot = axes[0]
weight_plot = axes[1]
calories_plot = weight_plot.twiny()
calories_plot.get_shared_x_axes().join(calories_plot, workouts_plot)

IN-USE Plot workouts

# Plot this first, because otherwise the shared x axis is restricted
# to the range of the workouts data, which is smaller at the moment.
# I wish order didn't matter so much...

# Version without merging...which fixed it!  No more jagged edges on the food/calorie/weight data!
workouts_frame = frames['workouts']
series_types = workouts_frame.Type.unique().tolist()
movement_types = workouts_frame.Movement.unique().tolist()

workouts_plot.set_color_cycle(solarized_colors)

for st in series_types:
    for m in movement_types:
        f = workouts_frame.query("Type == \"%s\" and Movement == \"%s\"" % (st, m))
        if not f.empty:
            f.Reps.plot(sharex=workouts_plot, ax=workouts_plot, label=m, marker=SERIES_MARKERS[st])

# **** Label workout lines

lines = workouts_plot.get_lines()
num_lines = len(lines)
xmin, xmax = plot.xlim()

x_values = numpy.linspace(xmin, xmax, num_lines * 200).tolist()
skip = int(len(x_values) * 0.2)
x_values = x_values[skip:-skip]
random.shuffle(x_values)
ymin, ymax = workouts_plot.get_ylim()
valid_y_min = ymin + ymin * 0.1
valid_y_max = ymax - ymax * 0.1

def sort_lines(lines):
    return sorted(lines, key=line_last_point, reverse=True)

def line_last_point(line):
    return line.get_xdata()[-1]

annotations = []

num_lines = len(lines)
if num_lines == 0:
    num_lines = 1
  
reduce_alpha_by = 1.0 / float(num_lines)
starting_alpha = 1.0 - reduce_alpha_by * num_lines

lines_sorted = sort_lines(lines)[0:int(len(lines) * WORKOUT_LABELS_FACTOR)]

if lines_sorted:
    for i, line in enumerate(lines_sorted):
        label = line.get_label()
        label_length = len(label)

        x_offset = 0

        # Convert dates to x-axis positions
        x_data = [date2num(d) for d in line.get_xdata()]

        # Choose random spot within line x values
        #x = random.choice(linspace(min(x_data), max(x_data), 100).tolist()[10:-10])

        # Choose random x point
        x = random.choice(x_data)

        y_data = line.get_ydata()

        # From <http://stackoverflow.com/a/39402483/712624>
        # Find corresponding y co-ordinate and angle of the...?
        ip = 0
        # Not sure if this is needed but keeping it for now
        # for i in range(len(x_data)):
        #     if x < x_data[i]:
        #         ip = i
        #         break
        if len(x_data) == 1:
            x = x_data[0]
            y = y_data[0]
            x_offset = random.randint(-20, 20)
        else:
            y = y_data[ip-1] + (y_data[ip] - y_data[ip-1]) * (x - x_data[ip-1]) / (x_data[ip] - x_data[ip-1])

        # # Compute text y-offset
        # y_offset = random.randint(-40, 40)
        # if y > valid_y_max:
        #     y_offset = random.randint(-20, 0)
        # elif y < valid_y_min:
        #     y_offset = random.randint(0, 20)

        # x_offset = 0
        # y_offset = 0

        # Calculate alpha
        alpha = starting_alpha + (reduce_alpha_by * i)
        if alpha < 0.25:
            alpha = 0.25

        # Make annotation
        annotations.append(workouts_plot.text(x, y, line.get_label(), size=6, alpha=alpha,
                                         color=line.get_color(),
                                         bbox=dict(boxstyle='round,pad=0.25', ec='#002b36', fc='#002b36', alpha=0.5)))

    x_points = [date2num(p)
                for p in line.get_xdata()
                for line in workouts_plot.get_lines()]
    y_points = [p
                for p in line.get_ydata()
                for line in workouts_plot.get_lines()]

    import adjustText
    adjustText.adjust_text(annotations, ax=workouts_plot, arrowprops=dict(arrowstyle='->', alpha=0.5, fill=False), force_points=2, force_text=2,
                           #only_move={'text': 'y', 'points': 'y'},
                           # precision=-1, expand_text=(50,100), expand_points=(10,40)
                           expand_points=(2,2)
    )

IN-USE Plot goal

combined_frame.AdjustedGoal = combined_frame.Goal + combined_frame.Burned.fillna(0)
goal_plot = combined_frame.AdjustedGoal.rolling(window=HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Calorie goal",
                                                                          color=ColorConverter().to_rgba(c.red, 0.75))

IN-USE Plot weekly sum

# Plot weekly sum
latest_date = combined_frame.index.values[-1]
weekly_cost = combined_frame.Price / 10
weekly_cost = weekly_cost.resample('W-SAT').sum()
weekly_cost = weekly_cost[weekly_cost.index < latest_date]  # Chop off extended data from resampling to next Saturday

weekly_cost.plot(ax=calories_plot, secondary_y=True, label="Weekly cost * 10", color="#546E00")

IN-USE Remaining calories today

now = datetime.now()
today = datetime(now.year, now.month, now.day)
try:
    calories_left_today = combined_frame.AdjustedGoal.ix[today] - combined_frame.Calories.ix[today]
except:
    calories_left_today = "unknown"

IN-USE Plot data

<<setup-figure>>

# *** Plot workouts

<<plot-workouts>>

# *** Plot weight

combined_frame.Weight.interpolate('time').plot(ax=weight_plot, color=ColorConverter().to_rgba(c.blue, ALPHA), label="_weight")
combined_frame.Weight.interpolate('time').rolling(WINDOW).mean().plot(ax=weight_plot, color=c.blue, label="Weight", title=None)

# *** Plot goal

<<plot-goal>>

# *** Plot calories

combined_frame.Calories.interpolate('time').plot(ax=calories_plot, secondary_y=True, label='_calories', color=ColorConverter().to_rgba(c.yellow, ALPHA))
combined_frame.Calories.interpolate('time').rolling(window=HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Calories in",
                                                                                    color=ColorConverter().to_rgba(c.yellow))

# *** Plot burned

combined_frame.Burned.fillna(0).rolling(HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Burned", color=c.orange)

# *** Plot Protein

combined_frame.Protein *= 10
combined_frame.Protein.rolling('1d').sum().rolling(window=HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Protein * 10", color=c.violet)

# *** Plot money

combined_frame.Price *= 100
combined_frame.Price.rolling(window=WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Price * 100",
                                                                                   color=c.green)

<<plot-weekly-sum>>

# *** Remaining calories today

<<remaining-calories-today>>

IN-USE Plot data w/Hodrick-Prescott filter

<<setup-figure>>

# *** Plot workouts

<<plot-workouts>>

# *** Plot weight

combined_frame.Weight.interpolate('time').plot(ax=weight_plot, color=ColorConverter().to_rgba(c.blue, ALPHA), label="_weight")
#  combined_frame.Weight.interpolate('time').rolling(WINDOW).mean().plot(ax=weight_plot, color=c.blue, label="Weight", title=None)

# *** Plot goal

<<plot-goal>>

# **** Plot weight with Hodrick-Prescott Filter

# http://statsmodels.sourceforge.net/devel/examples/notebooks/generated/tsa_filters.html#Hodrick-Prescott-Filter

def plot_hp(series, color='', label='', ax=None, secondary_y=False):
    import statsmodels.formula.api as sm
    import statsmodels.api as sa

    series_fixed = series.ffill().dropna()
    cycle, trend = sa.tsa.filters.hpfilter(series_fixed)
    trend.plot(color=color, label=label, ax=ax, secondary_y=secondary_y)

plot_hp(combined_frame.Weight, color=c.blue, label="Weight", ax=goal_plot, secondary_y=False)

# # Fill forward and remove empty values
# fixed_weight = combined_frame.Weight.ffill().dropna()

# import statsmodels.formula.api as sm
# import statsmodels.api as sa

# # Apply filter
# cycle, trend = sa.tsa.filters.hpfilter(fixed_weight)

# # Plot it
# trend.plot(color='#69B7F0', label="Weight H-P filtered")

# *** Plot calories

combined_frame.Calories.interpolate('time').plot(ax=calories_plot, secondary_y=True, label='_calories', color=ColorConverter().to_rgba(c.yellow, ALPHA))
#  combined_frame.Calories.interpolate('time').rolling(window=HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Calories", color=ColorConverter().to_rgba(c.yellow))

plot_hp(combined_frame.Calories, label="Calories in", color=ColorConverter().to_rgba(c.yellow), ax=calories_plot, secondary_y=True)

# *** Plot burned

#combined_frame.Burned.fillna(0).rolling(HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Burned", color=c.orange)

# *** Plot Protein

combined_frame.Protein *= 10
#  combined_frame.Protein.rolling('1d').sum().rolling(window=HALF_WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Protein * 10", color=c.violet)
plot_hp(combined_frame.Protein, ax=calories_plot, secondary_y=True, label="Protein * 10", color=c.violet)

# *** Plot money

combined_frame.Price *= 100
#  combined_frame.Price.rolling(window=WINDOW).mean().plot(ax=calories_plot, secondary_y=True, label="Price * 100", color=c.green)
plot_hp(combined_frame.Price, label="Cost * 100", color=c.green, ax=calories_plot, secondary_y=True)

<<plot-weekly-sum>>

# *** Remaining calories today

<<remaining-calories-today>>

IN-USE Set plot appearance w/month labels centered in the month

[2017-06-27 Tue 02:01] This works and looks great now. Only one drawback: If it’s before the 16th of the current month, there will be no label for the current month. I think I can live with that.

# NOTES:

# calories_plot is used for the year ticks, month_labels_plot is used
# for the month ticks.  month_labels_plot is copied from
# workouts_plot, but it could probably be copied from any of them.

# Background color of secondary plot (primary plot is transparent on top)
weight_plot.set_axis_bgcolor(c.base02)
workouts_plot.set_axis_bgcolor(c.base02)
calories_plot.set_axis_bgcolor(c.base02)

# Set spine colors
for spine in workouts_plot.spines.values() + weight_plot.spines.values() + calories_plot.spines.values():
    spine.set_edgecolor(border_color)

# Put primary plot (weight) above secondary plot
weight_plot.set_zorder(calories_plot.get_zorder() + 1)
weight_plot.patch.set_visible(False)
calories_plot.patch.set_visible(True)

# *** Ticks and grid

calories_plot.xaxis.set_minor_locator(WeekdayLocator(byweekday=[SA,SU]))
workouts_plot.xaxis.set_minor_locator(WeekdayLocator(byweekday=[SA,SU]))

# **** Set tick appearance

goal_plot.tick_params(labelcolor=c.base01, color=c.base03)
workouts_plot.yaxis.set_tick_params(labelcolor=c.base01, color=c.base03, labelright='on')
weight_plot.yaxis.set_tick_params(left='on', right='off', labelleft='on', labelright='off', labelcolor=c.base01, color=c.base03)

goal_plot.tick_params(which='both', color=c.base03)
workouts_plot.tick_params(which='both', color=c.base03, labelcolor=c.base01)
weight_plot.tick_params(which='both', color=c.base03)
calories_plot.tick_params(which='both', color=c.base03, labelcolor=c.base01)

# Apply year ticks to calories and workout plots
calories_plot.xaxis.set_major_locator(YearLocator())
calories_plot.xaxis.set_major_formatter(DateFormatter('%Y'))

# Disable rotation for and center year labels
for label in calories_plot.get_xmajorticklabels():
    label.set_rotation(0)
    label.set_horizontalalignment("center")

# Use weight_plot for month grid
weight_plot.set_xlabel('')
weight_plot.xaxis.set_major_locator(MonthLocator())
weight_plot.xaxis.set_tick_params(labelbottom='off')

# ***** Set January tick labels to bold year-number-only

# Make list of tick labels from both plots.  (I don't remember why I
# added the lists together, and I don't know why it works, but I'm not
# messing with it now!)
#labels = calories_plot.xaxis.get_majorticklabels() + workouts_plot.xaxis.get_majorticklabels()

# Modify the tick labels
#for num, tick in enumerate(major_ticks):

    # Center the tick labels.  This is an attribute of the text label
    # that is assigned to the tick, not of the tick itself.  (And who
    # knows why "center" is not the default, because plain numbers
    # seem to be centered.)
    #labels[num].set_horizontalalignment('center')

# Disable X-axis tick labels on calories plot (only displaying on workouts plot)
calories_plot.set_xlabel('')

# **** Make months-only x-axis

# See <https://matplotlib.org/devdocs/gallery/pylab_examples/centered_ticklabels.html>

# Make second x-axis plot
month_label_plot = workouts_plot.twiny()

# Copy X-axis values
month_label_plot.set_xbound(workouts_plot.get_xbound())

# Set major tick locator and formatter

# Using the 16th day of the month gives the best, most centered
# overall, but February is too far to the right.  Probably not
# possible to fix that without writing a MonthLocator that uses the
# length of the month, and probably not worth the trouble.
month_label_plot.xaxis.set_major_locator(MonthLocator(bymonthday=16))
month_label_plot.xaxis.set_major_formatter(DateFormatter('%b'))

# Align tick labels at center
for tick in month_label_plot.xaxis.get_major_ticks():
    tick.label1.set_horizontalalignment('center')
    tick.label1.set_fontproperties(FontProperties(weight=1000))

# Show month ticks at bottom
month_label_plot.xaxis.tick_bottom()

# Hide ticks themselves
month_label_plot.xaxis.set_tick_params(bottom='off')

# Set color
month_label_plot.tick_params(axis='x', labelcolor=c.base01)


# **** Set grid appearance

calories_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
calories_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
calories_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
calories_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

workouts_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
workouts_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
workouts_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
workouts_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

weight_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
weight_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
weight_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
weight_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

# Draw grid below data (this sort of works...not sure)
weight_plot.set_axisbelow(True)
calories_plot.set_axisbelow(True)

# *** Legend

# **** Food/weight legend

# Get lines for legend (from each subplot)
legend_lines = weight_plot.get_lines() + calories_plot.get_lines() + goal_plot.get_lines()

# Filter unwanted lines
legend_lines = [l for l in legend_lines
                if not l.get_label().startswith('_')]

# Setup legend
legend = weight_plot.legend([l for l in legend_lines],
                     [l.get_label() for l in legend_lines],
                     loc='upper left', fontsize='small', frameon=False, labelspacing=0.25, ncol=2)
legend.get_frame().set_facecolor(c.base02)
for text in legend.get_texts():
    text.set_color(c.base01)

# **** Workouts legend
# Good info: http://matplotlib.org/users/legend_guide.html#plotting-guide-legend

# Make line objects for legend
color_generator = itertools.cycle(solarized_colors)
workout_legend_lines = [mlines.Line2D([], [], marker=m, label=n, color=c.base0 # color_generator.next()
                                  )
                        for n, m in SERIES_MARKERS.iteritems()]

workout_legend_lines.sort(key=lambda l: l.get_label())

workouts_legend = workouts_plot.legend(handles=workout_legend_lines, numpoints=1, markerscale=0.75, ncol=2,
                                       loc='upper left', fontsize='small', frameon=False, labelspacing=0.25)
workouts_legend.get_frame().set_facecolor(c.base02)
for text in workouts_legend.get_texts():
    text.set_color(c.base01)

# *** Remaining calories today

calories_left_color = c.base0

today = datetime.today().date()

# If it's really late/early, I probably think of it as the day before midnight
if datetime.now().hour < 5:
    today = today - timedelta(days=1)

# Try to get the calories left for today.  If it fails, we don't have
# data for today yet, so show the goal.
try:
    calories_left_today = combined_frame.AdjustedGoal.ix[today] - combined_frame.Calories.ix[today]
    if calories_left_today < 0: calories_left_color = c.orange
    calories_left_today = '%s - %s = %s' % (int(combined_frame.AdjustedGoal.ix[today]),
                                            int(combined_frame.Calories.ix[today]),
                                            int(calories_left_today))
except:
    calories_left_today = int(combined_frame.AdjustedGoal[-1])

plot.annotate('Calories left today: %s' % calories_left_today,
              xy=(0, 0), xytext=(0.375, 0.01), xycoords='figure fraction', color=calories_left_color, size='small')

# If I want to change the color of part of the text in the future,
# these might work:
# https://stackoverflow.com/a/9185851/712624
# http://matplotlib.1069221.n5.nabble.com/Partial-coloring-of-text-in-matplotlib-tp27424p27435.html

# *** Set tight layout

# Do this after changing everything else

fig.tight_layout(h_pad=-0.5)

for ax in [workouts_plot, weight_plot, calories_plot, goal_plot]:
    ax.margins(0)

EXPERIMENTAL Set plot-appearance w/secondary legend in workout plot

# Background color of secondary plot (primary plot is transparent on top)
weight_plot.set_axis_bgcolor(c.base02)
workouts_plot.set_axis_bgcolor(c.base02)
calories_plot.set_axis_bgcolor(c.base02)

# Set spine colors
for spine in workouts_plot.spines.values() + weight_plot.spines.values() + calories_plot.spines.values():
    spine.set_edgecolor(border_color)

# Put primary plot (weight) above secondary plot
weight_plot.set_zorder(calories_plot.get_zorder() + 1)
weight_plot.patch.set_visible(False)
calories_plot.patch.set_visible(True)

# *** Ticks and grid

calories_plot.xaxis.set_minor_locator(WeekdayLocator(byweekday=[SA,SU]))
workouts_plot.xaxis.set_minor_locator(WeekdayLocator(byweekday=[SA,SU]))

# Set tick appearance

goal_plot.tick_params(labelcolor=c.base01, color=c.base03)
workouts_plot.yaxis.set_tick_params(labelcolor=c.base01, color=c.base03, labelright='on')
weight_plot.yaxis.set_tick_params(left='on', right='off', labelleft='on', labelright='off', labelcolor=c.base01, color=c.base03)

goal_plot.tick_params(which='both', color=c.base03)
workouts_plot.tick_params(which='both', color=c.base03)
weight_plot.tick_params(which='both', color=c.base03)
calories_plot.tick_params(which='both', color=c.base03)


#calories_plot.xaxis.set_visible(False)
weight_plot.xaxis.set_visible(False)

# Make major x-axis ticks manually (instead of using the
# major_locator, because it only makes the ticks when the plot is
# shown, and we need the ticks so we can change the labels)
major_ticks = MonthLocator().tick_values(combined_frame.index[0], combined_frame.index[-1])
calories_plot.xaxis.set_ticks(major_ticks)
workouts_plot.xaxis.set_ticks(major_ticks)

# Set January tick labels to bold year-number-only
labels = calories_plot.xaxis.get_majorticklabels() + workouts_plot.xaxis.get_majorticklabels()
for num, tick in enumerate(major_ticks):
    date = num2date(tick)
    if date.month == 1 and date.day == 1 and date.hour == 0 and date.minute == 0 and date.second == 0:
        labels[num].set_text(date.strftime('%Y'))
        labels[num].set_fontproperties(FontProperties(weight=1000))
    else:
        labels[num].set_text(date.strftime('%b'))
        labels[num].set_fontproperties(FontProperties(weight=1000))
calories_plot.xaxis.set_ticklabels(labels, rotation=0, color=c.base01)
workouts_plot.xaxis.set_ticklabels(labels, rotation=0, color=c.base01)


#weight_plot.xaxis.set_ticklabels([])
calories_plot.set_xlabel('')



# Set grid appearance
calories_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
calories_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
calories_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
calories_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

workouts_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
workouts_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
workouts_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
workouts_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)

weight_plot.grid(which='major', axis='x', linestyle='-', linewidth=1, color=c.base03)
weight_plot.grid(which='minor', linestyle=':', linewidth=1, color=c.base03)
weight_plot.grid(which='major', axis='y', linestyle='-', color=c.base03)
weight_plot.grid(which='minor', axis='y', linestyle=':', color=c.base03)


# Draw grid below data (this sort of works...not sure)
weight_plot.set_axisbelow(True)
calories_plot.set_axisbelow(True)

# *** Legend

# Get lines for legend (from each subplot)
legend_lines = weight_plot.get_lines() + calories_plot.get_lines() + goal_plot.get_lines()

# Filter unwanted lines
legend_lines = [l for l in legend_lines
                if not l.get_label().startswith('_')]

# Setup legend
legend = weight_plot.legend([l for l in legend_lines],
                     [l.get_label() for l in legend_lines],
                     loc='upper left', fontsize='small', frameon=False, labelspacing=0.25)
legend.get_frame().set_facecolor(c.base02)
for text in legend.get_texts():
    text.set_color(c.base01)

# **** Workout-type legend
# Good info: http://matplotlib.org/users/legend_guide.html#plotting-guide-legend

# Make line objects for legend
color_generator = itertools.cycle(solarized_colors)
workout_legend_lines = [mlines.Line2D([], [], marker=m, label=n, color=c.base0 # color_generator.next()
                                  )
                        for n, m in SERIES_MARKERS.iteritems()]

workouts_legend = workouts_plot.legend(handles=workout_legend_lines, numpoints=1, markerscale=0.75,
                                       loc='upper left', fontsize='small', frameon=False, labelspacing=0.25)
workouts_legend.get_frame().set_facecolor(c.base02)
for text in workouts_legend.get_texts():
    text.set_color(c.base01)

# Add set-type legend to Axes object <http://matplotlib.org/users/legend_guide.html#multiple-legends-on-the-same-axes>
workouts_plot.add_artist(workouts_legend)

# **** Movement legend

# Create legend artist
workout_lines = workouts_plot.get_lines()
movement_legend = workouts_plot.legend(handles=workout_lines, loc='upper right', ncol=6,
                                       fontsize=4, frameon=False, labelspacing=0.25,
                                       markerscale=0.75, numpoints=1)

# Set label colors
for num, text in enumerate(movement_legend.get_texts()):
    text.set_color(workout_lines[num].get_color())

IN-USE Save plot

# Write file and filename
filename = os.path.expanduser(filename)
fig.savefig(filename, facecolor=c.base03, pad_inches=0, bbox_inches='tight', dpi=DPI)

# Display in window
#plot.show()

IN-USE Main

# * Imports

<<imports>>

# * Constants

<<constants>>

# * Functions

<<functions>>

# * main

# ** Process data

<<process-data>>

# ** Plot data

<<plot-data>>

# ** Set plot appearance

<<set-plot-appearance>>

# ** Save plot

<<save-plot>>

IN-USE Main w/Hodrick-Prescott filter

# * Imports

<<imports>>

# * Constants

<<constants>>

# * Functions

<<functions>>

# * main

# ** Process data

<<process-data>>

# ** Plot data

<<plot-data-hp>>

# ** Set plot appearance

<<set-plot-appearance>>

# ** Save plot

<<save-plot>>

IN-USE Solarized colors class

class SolarizedColors (object):
    yellow   = '#b58900'
    orange   = '#cb4b16'
    red      = '#dc322f'
    magenta  = '#d33682'
    violet   = '#6c71c4'
    blue     = '#268bd2'
    cyan     = '#2aa198'
    green    = '#859900'

    base0 = '#839496'
    base00 = '#657b83'
    base1 = '#93a1a1'
    base01 = '#586e75'
    base2 = '#eee8d5'
    base02 = '#073642'
    base3 = '#fdf6e3'
    base03 = '#002b36'

    yellow_hc = '#DEB542'
    yellow_lc = '#7B6000'
    orange_hc = '#F2804F'
    orange_lc = '#8B2C02'
    red_hc = '#FF6E64'
    red_lc = '#990A1B'
    magenta_hc = '#F771AC'
    magenta_lc = '#93115C'
    violet_hc = '#9EA0E5'
    violet_lc = '#3F4D91'
    blue_hc  = '#69B7F0'
    blue_lc  = '#00629D'
    cyan_hc  = '#69CABF'
    cyan_lc  = '#00736F'
    green_hc = '#B4C342'
    green_lc = '#546E00'

EXPERIMENTAL Plot med

# Load data
med = pandas.read_csv("~/.log/med", sep=">", header=None)
med.columns = ['date', 'comments']
med['date'] = med['date'].apply(pandas.to_datetime, format="<%Y-%m-%d %a %H:%M")
med = med.set_index(['date'])

# Add column to help with plotting (need a y-value that's on the plot)
med = med.assign(number=calories_plot.get_ylim()[0])

# Plot data
calories_plot.plot(med.number, marker="^", c="white")

[2017-07-08 Sat 15:04] This works, but I’m not going to use it right now; not sure how useful it really is, especially with a 6-month view.

IN-USE Money table code

# * Imports

import os, re, sys

from datetime import datetime, timedelta

import matplotlib.pyplot as plot

from matplotlib.dates import DateFormatter, MonthLocator, WeekdayLocator, num2date, SU
from matplotlib.font_manager import FontProperties
from matplotlib.colors import ColorConverter

import pandas, numpy

# * Constants

FOOD_DATE_FORMAT = "[%Y-%m-%d %a]"

OLDEST_DATE = datetime.strptime(data[1][1], ORG_DATE_FORMAT) \
              if DAYS == "all" \
                 else datetime.now() - timedelta(days=DAYS)

# * main

# ** Process data

# Load into frames
df = pandas.DataFrame.from_records(food_data[1:], columns=[''] + food_data_header)

# Drop unwanted columns
df = df[['Date', 'Price']]

# Convert date fields to datetime objects
df.Date = df.Date.apply(pandas.to_datetime, format=FOOD_DATE_FORMAT)

# Drop empty rows
df = df[df.Price != numpy.nan]
df = df[df.Price != '']

# Drop old data
df = df[df.Date > OLDEST_DATE]

# Set date as index
df = df.set_index(['Date'])

weekly = df.resample('W-SAT').sum()
daily = df.resample('1d').sum()

def format_float(val):
      return '{:.2f}'.format(val)

weekly.Price = weekly.Price.apply(format_float)
daily.Price = daily.Price.apply(format_float)

def fixdate(row):
      return (str(row[0]).split()[0], row[1])

result = [['Weekly','-----']]
result.extend([fixdate(r) for r in reversed(weekly.to_records())])
result.append(['Daily','-----'])
result.extend([fixdate(r) for r in reversed(daily.to_records())])

return result

IN-USE Optimal day

import numpy, pandas

FOOD_DATE_FORMAT = "[%Y-%m-%d %a]"

# Load into frames
df = pandas.DataFrame.from_records(food_data[1:], columns=['ID', 'Date', None, 'Calories', 'Protein', 'Price', None])

# Drop unwanted columns
df = df[['Date', 'Calories', 'Protein', 'Price']]

# Convert date fields to datetime objects
df.Date = df.Date.apply(pandas.to_datetime, format=FOOD_DATE_FORMAT)

# Convert missing values to NaN for later dropping
for col in ['Calories', 'Protein', 'Price']:
    df[col] = df[col].apply(lambda p: numpy.nan if not p else p)

# # Drop old data
# df = df[df.Date > OLDEST_DATE]

# Set date as index
df = df.set_index(['Date'])

# Resample, sum, and drop rows with missing values
df = df.resample('1d').sum().dropna()

# Calculate ratios
df['protein_per_dollar'] = df['Protein'] / df['Price']
df['calories_per_dollar'] = df['Calories'] / df['Price']
df['calories_per_protein'] = df.Calories / df.Protein

# Round off (I don't know how to do this all at once)
df['protein_per_dollar'] = df['protein_per_dollar'].apply(int)
df['calories_per_dollar'] = df['calories_per_dollar'].apply(int)
df['calories_per_protein'] = df['calories_per_protein'].apply(int)


# print "Best calorie/$ days"
# print df.sort('calories_per_dollar', ascending=False)[:5]

# print "Best protein/$ days"
# print df.sort('protein_per_dollar', ascending=False)[:5]

# Sort direction for "best" version of each report
sort_key = {'calories_per_dollar': False,
            'protein_per_dollar': False,
            'calories_per_protein': True}

result = []

def fix_header(s):
    s = s.replace("_per", "/")
    s = s.replace("_protein", "protein")
    s = s.replace("_dollar", "$")

    return s

# New
for direction in [["----- Best", False], ["---- Worst", True]]:
    invert_sort = direction[1]

    # Header
    result.append([direction[0], "-"*20, '-'*5])

    for col, sort_dir in sort_key.iteritems():
        # Decide sorting order
        if invert_sort:
            sort_dir = not sort_dir

        # Header
        result.append(['Date', fix_header(col).capitalize(), 'Cost'])

        # Append data to result
        for r in df.sort(col, ascending=sort_dir)[:5].itertuples():
            r = r._asdict()
            result.append([r['Index'].__str__().split()[0], r[col], round(r['Price'], 2)])
        result.append(['', ''])


return result
#print result

IN-USE List food by

IN-USE Protein

(org-fitness-list-food-by "protein" :reverse t)

WIP Logging the previous day

I think I can do this with this code that’s used for org-todo-yesterday:

(defun org-current-effective-time ()
  "Return current time adjusted for `org-extend-today-until' variable."
  (let* ((ct (org-current-time))
         (dct (decode-time ct))
         (ct1
          (cond
           (org-use-last-clock-out-time-as-effective-time
            (or (org-clock-get-last-clock-out-time) ct))
           ((and org-use-effective-time (< (nth 2 dct) org-extend-today-until))
            (encode-time 0 59 23 (1- (nth 3 dct)) (nth 4 dct) (nth 5 dct)))
           (t ct))))
    ct1))

(defun org-todo-yesterday (&optional arg)
  "Like `org-todo' but the time of change will be 23:59 of yesterday."
  (interactive "P")
  (if (eq major-mode 'org-agenda-mode)
      (apply 'org-agenda-todo-yesterday arg)
    (let* ((hour (third (decode-time
                         (org-current-time))))
           (org-extend-today-until (1+ hour)))
      (org-todo arg))))

Config

all
7d
0.15
0.25
1.55
  ;;; Requirements

;; Requires Org 9.0 now

(require 'dash)
(require 's)
(require 'solarized)

  ;;; RMR and daily caloric expenditure

;; Verified to match RMR from Mifflin, St. Jeor, et al method as
;; described at http://www.calculateyourrmr.com/ which is the same as
;; the one in YAYOG

(defvar org-fitness-desired-loss-rate 0.5
  "Desired weight-loss rate in pounds-per-week.")

;; Activity multiplier (sedentary=1.2, moderate/YAYOG=1.55)
(defvar org-fitness-activity-multiplier 1.2)

(defvar org-fitness-birthday-encoded (encode-time 0 0 0 1 1 1984))
(defvar org-fitness-height-inches 69)

;;;; Code

(defun org-fitness-age ()
  (let* ((current-ts (float-time (current-time)))
         (birthday-ts (float-time org-fitness-birthday-encoded))
         (difference (- current-ts birthday-ts))
         (seconds-per-year (* 60 60 24 365)))
    (/ difference seconds-per-year)))

(defun org-fitness-rmr ()
  "Calculate RMR in kcal from data."
  (let* ((weight-lbs (string-to-number (caar (last (org-fitness-select-columns "weight-log" '("Weight"))))))
         (weight-kg (/ weight-lbs 2.2))
         (height-in org-fitness-height-inches)
         (height-cm (* height-in 2.54))
         (age (org-fitness-age)))
    (+ (- (+ (* 10 weight-kg)
             (* 6.25 height-cm))
          (* 5 age))
       5)))

(defun org-fitness-daily-caloric-expenditure ()
  "Return daily kcal expenditure from RMR and activity multiplier."
  (* (org-fitness-rmr) org-fitness-activity-multiplier))

(defun org-fitness-daily-calorie-deficit ()
  "Return daily caloric deficit based on desired pounds-per-week loss rate."
  (/ (* 3500 org-fitness-desired-loss-rate) 7))

(defun org-fitness-daily-calorie-goal ()
  "Return daily calorie goal."
  (round (- (org-fitness-daily-caloric-expenditure) (org-fitness-daily-calorie-deficit))))

  ;;; Functions

(defmacro org-fitness-number-or-string (val)
  (let ((gval (gensym))
        (gnum (gensym)))
    `(let* ((,gval ,val)
            (,gnum (string-to-number ,gval)))
       (if (or (string= ,gval "")  ; In the case of free food, I might prefer an empty string over a 0.00
               (string= ,gval "0")
               (string= ,gval "0.0")
               (string= ,gval "0.00")
               (< 0 ,gnum))
           ;; Number
           ,gnum
         ;; String
         ,gval))))

(defun org-fitness-table-data-without-hlines (table-name)
  "Return table data as list without hline rows."
  (org-with-table table-name
    (--remove (equal 'hline it)
              (org-table-to-lisp))))

(defun org-fitness-sum-table-lines ()
  "Sum each numeric column in table lines touched by the region."
  (interactive)
  (org-with-wide-buffer
   (let* (
          ;; Add empty column because (org-table-get-specials) leaves the empty one out, which throws off the indices
          (header (cons nil (org-table-column-names)))
          (start (save-excursion
                   (goto-line (line-number-at-pos (region-beginning)))
                   (line-beginning-position)))
          (end (save-excursion
                 (goto-line (line-number-at-pos (region-end)))
                 (line-end-position)))
          (lines (buffer-substring-no-properties start end))
          (table (--remove (equal 'hline it)
                           (org-table-to-lisp lines)))
          (indices (cdr  ; Drop index representing first column, which is always empty
                    (butlast  ; Drop index representing last column, which is comments
                     (-find-indices (lambda (col)
                                      (or (string= col "")  ; In the case of free food, I might prefer an empty string over a 0.00
                                          (string= col "0")
                                          (string= col "0.0")
                                          (string= col "0.00")
                                          (< 0 (string-to-number col))))
                                    (car table)))))
          (sums (cl-loop for i in indices
                         collect (-reduce '+ (-map 'string-to-number
                                                   (-select-column i table)))))
          (result (-zip (-select-by-indices indices header) sums)))
     (org-fitness-display-values result :prefix "Lines: "))))

(defun org-fitness-get-column-index (column header)
  "Return index of column named COLUMN according to HEADER."
  (--find-index (string= column it) header))

(defun org-fitness-summarize-food-list (food-list)
  "Print message to minibuffer summarizing food data in FOOD-LIST.
    FOOD-LIST should be the food-log Org table converted to a list."
  (let* ((header (car food-list))
         (data (cdr food-list))
         (calories-index (org-fitness-get-column-index "Calories" header))
         (protein-index (org-fitness-get-column-index "Protein" header))
         (cost-index (org-fitness-get-column-index "Price" header))
         (calories (-reduce '+ (-map 'string-to-number (-select-column calories-index data))))
         (protein (-reduce '+ (-map 'string-to-number (-select-column protein-index data))))
         (cost (-reduce '+ (-map 'string-to-number (-select-column cost-index data))))
         (calories-per-dollar (/ calories cost))
         (protein-per-dollar (/ protein cost))
         (calories-per-protein (/ calories protein))
         (calories-string (org-fitness-colorize-string "Calories" org-fitness-calories-color))
         (protein-string (org-fitness-colorize-string "Protein" org-fitness-protein-color))
         (cost-string (org-fitness-colorize-string "Cost" org-fitness-cost-color))
         (cost (format "%.2f" cost)))
    (message "%s: %s (%d/$) | %s: %sg (%d/$) (%d cal/g) | %s: %s"
             "Calories" (org-fitness-colorize-string calories org-fitness-calories-color :weight 'bold) calories-per-dollar
             "Protein" (org-fitness-colorize-string protein org-fitness-protein-color :weight 'bold) protein-per-dollar calories-per-protein
             "Cost" (org-fitness-colorize-string cost org-fitness-cost-color :weight 'bold))))

(defun org-fitness-summarize-food-table-lines ()
  "Summarize data in food-log table touched by the region."
  (interactive)
  (org-with-wide-buffer
   (let* ((header (cons nil (org-table-column-names)))
          (start (save-excursion
                   (goto-line (line-number-at-pos (region-beginning)))
                   (line-beginning-position)))
          (end (save-excursion
                 (goto-line (line-number-at-pos (region-end)))
                 (line-end-position)))
          (lines (buffer-substring-no-properties start end))
          (table (--remove (equal 'hline it)
                           (org-table-to-lisp lines))))
     (org-fitness-summarize-food-list (cons header table)))))

(defun org-fitness-display-values (values &key region)
  "Display list of data in minibuffer, colorized according to ht.
      VALUES should be a list of (NAME . VALUE) pairs."
  (message (concat region (s-join " | "
                                  (--map (org-fitness-colorize-pair it)
                                         values)))))

(defun org-fitness-colorize-pair (pair)
  (let* ((name (car pair))
         (color (ht-get org-fitness-colors-ht name))
         (value (cdr pair))
         (value (if (stringp value)
                    value
                  (if (floatp value)
                      (format "%.2f" value)
                    (format "%s" value)))))
    (add-face-text-property 0 (length value) `(:foreground ,color :weight bold) nil value)
    (format "%s: %s" name value)))

(defun org-fitness-todays-timestamp ()
  "Return Org timestamp for today, or yesterday if before 4am."
  (let* ((decoded-time (decode-time))
         (hour (nth 2 decoded-time))
         (day (nth 3 decoded-time))
         encoded-time)
    (when (< hour 4)
      (setq decoded-time (-replace-at 3 (1- day) decoded-time)))
    (setq encoded-time (apply 'encode-time decoded-time))
    (with-temp-buffer
      (org-insert-time-stamp encoded-time nil t)
      (buffer-string))))

(defun org-fitness-summarize-food-for-day (&optional date)
  "Display sums of food data for the day at point.
  If DATE is supplied (as an Org timestamp), display the data for
  that date.  Otherwise, if point in an Org table, use the date
  column.  Otherwise, use today's date."
  (interactive)
  (let* ((date (cond
                (date date)
                ((org-at-table-p)
                 ;; In a table; get date field
                 ;; TODO: Use a function to get the date column index
                 (let ((date (org-with-wide-buffer (org-table-get-field 2))))
                   (if (or (string-empty-p date)
                           (string= "Date" (s-trim date)))
                       ;; In a table but not in a data row; use today
                       (org-fitness-todays-timestamp)
                     date)))
                (t  ;; Use today's date by default
                 (org-fitness-todays-timestamp))))
         (calories (org-fitness-sum-column "food-log" "Calories" date))
         (protein (org-fitness-sum-column "food-log" "Protein" date))
         (cost (org-fitness-sum-column "food-log" "Price" date))
         (calories-per-dollar (/ calories cost))
         (protein-per-dollar (/ protein cost))
         (calories-per-protein (/ calories protein))
         (calories-string (org-fitness-colorize-string "Calories" org-fitness-calories-color))
         (protein-string (org-fitness-colorize-string "Protein" org-fitness-protein-color))
         (cost-string (org-fitness-colorize-string "Cost" org-fitness-cost-color))
         (cost (format "%.2f" cost)))

    (message "%s: %s (%d/$) | %s: %sg (%d/$) (%d cal/g) | %s: %s"
             "Calories" (org-fitness-colorize-string calories org-fitness-calories-color :weight 'bold) calories-per-dollar
             "Protein" (org-fitness-colorize-string protein org-fitness-protein-color :weight 'bold) protein-per-dollar calories-per-protein
             "Cost" (org-fitness-colorize-string cost org-fitness-cost-color :weight 'bold))))

(defun org-fitness-colorize-string (s color &rest rest)
  "Add COLOR property and other properties REST to string S.
       If S is not a string, format it into one."
  (unless (stringp s)
    (setq s (format "%s" s)))
  (add-face-text-property 0 (length s) `(:foreground ,color ,@rest) nil s)
  s)

(defun org-fitness-goto-table (name)
  "Go to table named NAME if point is not in any table."
  (unless (org-at-table-p)
    (let ((org-babel-results-keyword "NAME"))
      (org-babel-goto-named-result name)
      (forward-line 2))))

(defun org-fitness-sum-rectangle ()
  "Sum values in marked rectangle."
  (interactive)
  (message "%s: %.2f"
           (org-fitness-column-name-at-point)
           (->> (extract-rectangle (region-beginning) (region-end))
                (-map 'string-to-number)
                (-sum))))

(defmacro org-with-table (table-name &rest body)
  "Move point to inside Org table TABLE-NAME and execute BODY."
  (declare (indent defun))
  `(org-with-wide-buffer
    (let ((org-babel-results-keyword "NAME"))
      (org-babel-goto-named-result ,table-name)
      (forward-line 2)
      ,@body)))

(defun org-table-name-at-point ()
  "Return name of table at point."
  (org-with-wide-buffer
   (goto-char (org-table-begin))
   (forward-line -1)
   (beginning-of-line)
   (re-search-forward (rx "#+NAME:" (1+ space) (group (1+ (not space))) eol))
   (match-string-no-properties 1)))

(defun org-table-column-names (&optional table-name)
  "Return list of column names for TABLE-NAME or table at point."
  (org-with-table
    (or table-name (org-table-name-at-point))
    (org-table-analyze)
    (--map (org-no-properties (car it))
           org-table-column-names)))

(defun org-fitness-timestamp-at-point ()
  "Return any Org timestamp at point, or nil."
  (when (org-at-timestamp-p t) (match-string-no-properties 0)))

(defun org-fitness-column-names-at-point ()
  "Return list of column names for table at point."
  (org-table-analyze)
  (--map (org-no-properties (car it))
         org-table-column-names))

(defun org-fitness-column-name-at-point ()
  "Return name of column at point."
  (let ((column (org-table-current-column)))
    (org-with-wide-buffer
     (org-table-goto-line 0)
     (s-trim (substring-no-properties (org-table-get-field column))))))

(defun org-fitness-table-name-at-point ()
  (org-with-wide-buffer
   (goto-char (org-table-begin))
   (forward-line -1)
   (beginning-of-line)
   (re-search-forward (rx "#+NAME:" (1+ space) (group (1+ (not space))) eol))
   (match-string-no-properties 1)))

(defun org-fitness-sum-column (&optional table column date)
  "Return sum of COLUMN in TABLE for DATE.
       TABLE should be the name of an Org table. If nil and point is in
       a table, the current table will be used.

       DATE should be an Org timestamp. If nil and point is on a
       timestamp, DATE will be picked up from point.  If just nil, date
       will be ignored.

       COLUMN should be the name of a column's header field. If nil and
       the point is in an Org table, the name of the current column will
       be used."
  (interactive)
  (let* ((table (or table (org-fitness-table-name-at-point)))
         (column (or column (org-fitness-column-name-at-point)))
         (ts-at-point (org-fitness-timestamp-at-point))
         (date (or date
                   (when (and ts-at-point
                              (org-at-table-p))
                     ;; TODO: Use a function to get the date column index
                     (org-with-wide-buffer (org-table-get-field 2)))))
         (sum (-sum (-map 'string-to-number
                          (-flatten (org-fitness-select-columns table (list column) date))))))
    ;; (if (floatp sum)
    ;; (format "%0.2f" sum)
    ;; sum)
    sum))

(defun org-fitness-select-columns (table-name column-names &optional date)
  "Return list of rows with selected COLUMN-NAMES in TABLE-NAME for DATE.

  COLUMN-NAMES is a list of strings.

  If DATE is nil, ignore date.  If DATE is symbol `today', today's
  date will be used.

  This function expects the table to have a header row in which the
  date column is named \"Date\" and contains Org timestamps."
  (let* ((org-extend-today-until 4)
         (day-number (cond
                      ((null date) nil)
                      ((equal date 'today) (org-today))
                      (date (1+ (date-to-day date)))))
         (table-data (--remove (or (equal 'hline it)
                                   ;; Remove lines without a date (second column)
                                   (string-empty-p (nth 1 it)))
                               (org-with-table table-name
                                 (org-table-to-lisp))))
         (header (car table-data))
         (date-column-number (--find-index (string= "date" (downcase it)) header))
         (column-numbers
          ;; The indexes of the columns we need to "pre-select", including the date, even if the date is not being returned
          (-sort '< (-uniq (--map (-find-index (-partial 'string= it) header)
                                  column-names))))
         (final-columns
          ;; The adjusted indexes of the columns we're returning, after they've been pre-selected
          (number-sequence 1 (length column-numbers))))
    (->> (cdr table-data) ; Remove header
         (-select-columns (cons date-column-number column-numbers))
         ((lambda (row)
            (if (null day-number)
                row
              (--filter (= day-number
                           (->> (car it) ; Date column is first
                                (org-time-string-to-time)
                                (time-to-days)))
                        row))))

         ;; Remove date column if not requested
         (-select-columns final-columns))))

(defun org-fitness-remove-columns-by-indices (indices table)
  "Return TABLE without columns specified by INDICES.
      INDICES is a list of integers and TABLE is a list of lists."
  (let* ((num-columns (length (car table)))
         (columns (-remove (lambda (col)
                             (memq col indices))
                           (number-sequence 0 (1- num-columns)))))
    (-select-columns columns table)))

(defun org-fitness-call-src-blocks (names)
  "Call source blocks specified by NAMES.
       NAMES should be a list of symbols (not strings) matching the
       source blocks' \"#+NAME:\" header."
  ;; Based on <http://kitchingroup.cheme.cmu.edu/blog/2014/08/11/Using-org-mode-outside-of-Emacs-sort-of/>
  ;; This works better than the org-sbe (aka sbe) macro, because it
  ;; calls the block upon expansion, making it difficult to bind to
  ;; a command to run later
  (dolist (name names)
    (org-with-wide-buffer
     (-when-let (src (org-element-map (org-element-parse-buffer) 'src-block
                       (lambda (element)
                         (when (string= (symbol-name name) (org-element-property :name element))
                           element))
                       nil ;info
                       t ))
       (goto-char (org-element-property :begin src))
       (let ((org-confirm-babel-evaluate nil))
         (org-babel-execute-src-block))))))

  ;;;; Food-listing functions

(cl-defun org-fitness-list-food-by (sort-column &key reverse)
  "Return table in list form of food sorted by SORT-COLUMN.
        SORT-COLUMN is the name of a column according to the header row."
  (let* ((table-name "food-log")
         (table-data (org-fitness-table-data-without-hlines table-name))
         (header (car table-data))
         ;; Remove unwanted columns
         (date-col-index (--find-index (string= (downcase "date") (downcase it)) header))
         (table-data (org-fitness-remove-columns-by-indices (list 0 date-col-index) table-data))
         (header (car table-data))
         (sort-col-index (--find-index (string= (downcase sort-column) (downcase it)) header))
         (-compare-fn (lambda (a b)
                        ;; Compare first column (food name) in -uniq
                        (equal (car a) (car b))))
         (result (cons header
                       (->> (cdr table-data)
                            (--remove (member "Raw calorie data" it))
                            (-sort (lambda (row-a row-b)
                                     (let ((a-val (org-fitness-number-or-string (nth sort-col-index row-a)))
                                           (b-val (org-fitness-number-or-string (nth sort-col-index row-b))))
                                       (when (and (numberp a-val)
                                                  (numberp b-val))
                                         (< a-val b-val)))))
                            (-uniq)))))
    (if reverse
        (nreverse result)
      result)))

(cl-defun org-fitness-list-food-by-calories-per-protein (&key reverse)
  "Return table in list form of food sorted by calories per gram of protein."
  (let* ((table-name "food-log")
         (table-data (org-fitness-table-data-without-hlines table-name))
         (header (car table-data))
         ;; Remove unwanted columns
         (date-col-index (--find-index (string= (downcase "date") (downcase it)) header))
         (table-data (org-fitness-remove-columns-by-indices (list 0 date-col-index) table-data))
         (header (car table-data))
         (calories-col-index (--find-index (string= "Calories" it) header))
         (protein-col-index (--find-index (string= "Protein" it) header))
         (-compare-fn (lambda (a b)
                        ;; Compare first column (food name) in -uniq
                        (equal (car a) (car b))))
         (unique-foods (->> (cdr table-data)
                            (--remove (member "Raw calorie data" it))
                            (-uniq)))
         ;; Remove foods without protein
         (unique-foods (--remove (= 0 (org-fitness-number-or-string (nth protein-col-index it)))
                                 unique-foods))
         (analyzed-foods (-map (lambda (row)
                                 (let* ((calories (org-fitness-number-or-string (nth calories-col-index row)))
                                        (protein (org-fitness-number-or-string (nth protein-col-index row)))
                                        (calories-per-protein (when (> protein 0)
                                                                (round (/ calories protein)))))
                                   (-insert-at 1 calories-per-protein row)))
                               unique-foods))
         (result (cons (-insert-at 1 "Calories/Protein" header)
                       (-sort (lambda (a b)
                                (< (nth 1 a) (nth 1 b)))
                              analyzed-foods))))
    (if reverse
        (nreverse result)
      result)))

;;;; Capturing

(defun ap/get-unique-food-items ()
  (let ((buffer (get-buffer "fitness.org"))
        (table-name "food-log")
        (skip-lines 3)
        (-compare-fn (lambda (a b)
                       ;; Compare food names
                       (string= (nth 2 a)
                                (nth 2 b)))))
    (with-current-buffer buffer
      (org-with-wide-buffer
       (goto-char (point-min))
       ;; Find table
       (re-search-forward (rx-to-string `(: "#+NAME: " ,table-name)))
       (forward-line 1)
       (cl-loop for item in (->> (org-table-to-lisp)
                                 (-drop skip-lines)    ; 2 hlines and header
                                 (--remove (eq 'hline it))
                                 (-distinct))
                collect (cl-destructuring-bind (_ date name calories protein price comment) item
                          (list :name name :calories calories :protein protein :price price)))))))

(cl-defun ap/complete-food-items (&key (times 1) ask)
  "Return food data plist TIMES items long, completed with Helm.
Data entered may contain math expressions which will be evaluated
with `calc-eval'."
  (cl-macrolet ((ask (prompt value)
                     (let ((op-chars (list "+" "-" "*" "/")))
                       `(if (or ask (not ,value))
                            (progn
                              (setq value (read-from-minibuffer ,prompt ,value nil nil ,value))
                              (if (--any? (s-contains? it value) ',op-chars)
                                  ;; value contains math operation; eval it
                                  (format "%0.2f" (string-to-number (calc-eval value)))
                                value))
                          ;; Not asking and value is already set
                          ,value))))
    (cl-loop with food-data = (ap/get-unique-food-items)
             repeat times
             for selected-name = (helm-comp-read "Food: " (--map (plist-get it :name) food-data))
             for selected-data = (cl-loop for item in food-data
                                          if (string= (plist-get item :name) selected-name)
                                          return item)
             while selected-name
             ;; Would like to use -let here, but it doesn't seem to work when nested inside cl-loop
             collect (list :name selected-name
                           :calories (string-to-int (ask "Calories: " (plist-get selected-data :calories)))
                           :protein (string-to-int (ask "Protein: " (plist-get selected-data :protein)))
                           :price (format "%0.2f" (string-to-number (ask "Price: " (plist-get selected-data :price))))
                           :comment (ask "Comment: " "")))))

(cl-defun ap/complete-food-items-multi (&key (times 1) ask)
  "Return food data plist TIMES items long, completed with Helm.
Multiple items may be selected with Helm.  Numerical data entered
may contain math expressions which will be evaluated with
`calc-eval'."
  (cl-macrolet ((ask (prompt value)
                     (let ((op-chars (list "+" "-" "*" "/")))
                       `(if (or ask (not ,value))
                            (progn
                              (setq value (read-from-minibuffer ,prompt ,value nil nil ,value))
                              (if (--any? (s-contains? it value) ',op-chars)
                                  ;; value contains math operation; eval it
                                  (format "%0.2f" (string-to-number (calc-eval value)))
                                value))
                          ;; Not asking and value is already set
                          ,value))))
    (apply 'append
           (cl-loop with food-data = (ap/get-unique-food-items)
                    repeat times
                    for selected-names = (helm-comp-read "Food: " (--map (plist-get it :name) food-data)
                                                         :marked-candidates t)
                    for selected-items = (cl-loop for item in food-data
                                                  if (member (plist-get item :name) selected-names)
                                                  collect item)
                    do (when (and (null selected-items) selected-names)
                         ;; New food names, so ask for info
                         (setq selected-items (--map (list :name it) selected-names)))
                    ;; Would like to use -let here, but it doesn't seem to work when nested inside cl-loop
                    collect (cl-loop for item in selected-items
                                     collect (list :name (plist-get item :name)
                                                   :calories (string-to-number (ask "Calories: " (plist-get item :calories)))
                                                   :protein (string-to-number (ask "Protein: " (plist-get item :protein)))
                                                   :price (format "%0.2f" (string-to-number (ask "Price: " (plist-get item :price))))
                                                   :comment (ask "Comment: " "")))))))

(cl-defun ap/capture-food (prefix &key yesterday ask)
  (interactive "P")
  (switch-to-buffer (ap/org-get-file-buffer "fitness.org"))
  (let* ((table-name "food-log")
         (date-time (with-temp-buffer
                      (org-insert-time-stamp (org-current-time) nil t)
                      (when yesterday
                        (org-timestamp-down-day))
                      (buffer-string)))
         (insert-hline (not (string= (org-no-properties (org-table-get-remote-range table-name "@II$2"))
                                     date-time)))
         (foods (ap/complete-food-items-multi :times (prefix-numeric-value prefix) :ask ask))
         (pos (save-excursion
                (goto-char (point-min))
                (if (re-search-forward (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*" (regexp-quote table-name) "[ \t]*$") nil t)
                    (progn
                      (goto-char (match-beginning 0))
                      (forward-line 3)
                      (line-end-position))
                  (error "Unable to find %s table." table-name)))))
    (goto-char pos)
    ;; Insert new hline if necessary
    (when insert-hline
      (org-table-insert-hline t)
      (forward-line -1))
    (dolist (food foods)
      (end-of-line)
      (cl-destructuring-bind (&key name calories protein price comment) food
        (insert (format "\n|   | %s | %s | %s | %s | %s | %s |" date-time name calories protein price comment))))
    (backward-char 4)
    (org-table-justify-field-maybe)
    (call-interactively 'org-table-next-field)))

(defun ap/capture-food-ask (prefix)
  "Run `ap/capture-food' with `:ask' set."
  (interactive "P")
  (funcall 'ap/capture-food prefix :ask t))

(defun ap/capture-food-yesterday (prefix)
  (interactive "P")
  (ap/capture-food prefix :yesterday t))

;;;;;;; Workout-capturing

(cl-defun ap/org-complete-table-column (table column &key hline prompt selector)
  "Return unique value from COLUMN in TABLE with Helm completion.

If HLINE is specified, start after that number hline in table.

If SELECTOR is specified, use that instead of constructing one from TABLE, COLUMN, and HLINE.

If PROMPT is specified, use it as prompt in minibuffer."
  (unless prompt
    (setq prompt (concat (capitalize column) ": ")))
  (when hline
    (setq hline (s-repeat hline "I")))
  (unless selector
    (setq selector (concat "@" hline "$" column ".." "@>" "$" column)))
  (helm-comp-read prompt (->> (org-table-get-remote-range table selector)
                              (-map 'org-no-properties)
                              (-uniq)
                              (-sort 'string<))))

(defun ap/capture-workout-data (prefix)
  (interactive "P")
  (switch-to-buffer (ap/org-get-file-buffer "fitness.org"))
  (--dotimes (if prefix (prefix-numeric-value prefix) 1)
    (let* ((date-time (with-temp-buffer
                        (org-insert-time-stamp (org-current-time) nil t)))
           (insert-hline (not (string= (org-no-properties (org-table-get-remote-range "workout-log" "@II$Date"))
                                       date-time)))
           (movement (ap/org-complete-table-column "workout-log" "Movement" :hline 2))
           (type (ap/org-complete-table-column "workout-log" "Type" :hline 2))
           (reps (read-from-minibuffer "Reps: "))
           (comment (read-from-minibuffer "Comment: "))
           (pos (save-excursion
                  (goto-char (point-min))
                  (if (re-search-forward (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*" (regexp-quote "workout-log") "[ \t]*$") nil t)
                      (progn
                        (goto-char (match-beginning 0))
                        (forward-line 3)
                        (line-end-position))
                    (error "Unable to find workout-log table.")))))
      (goto-char pos)
      (when insert-hline
        (setq insert-hline nil)       ; Only insert one line
        (org-table-insert-hline t)
        (forward-line -1))
      (end-of-line)
      (insert (format "\n|   | %s | %s | %s | %s | %s |" date-time movement reps type comment))
      (backward-char 4)
      (org-table-justify-field-maybe)
      (call-interactively 'org-table-next-field))))

;;; Make keymap and bind keys

(use-local-map (copy-keymap org-mode-map))

(cl-macrolet ((bind-kli (&rest forms)
                        ;; This works because somehow splicing into the
                        ;; explicit progn works, even though there's also
                        ;; an implicit progn around the explicit progn
                        (let ((res (-map
                                    (-lambda ((key . body))
                                      `(local-set-key (kbd ,key)
                                                      (lambda (prefix)
                                                        (interactive "P")
                                                        ,@body)))
                                    forms)))
                          `(progn ,@res))))
  (bind-kli
   ("C-l"
    (helm-org-in-buffer-headings)
    (recenter-top-bottom 1))
   ("C-c C-h"
    (progn (unless (org-at-table-p)
             (org-fitness-goto-table "food-log")
             (org-table-goto-line 2)))
    (org-fitness-summarize-food-for-day)
    (hydra-org-fitness/body))
   ("C-c C-f"
    (ap/capture-food prefix)
    (org-fitness-call-src-blocks '(modular-plotting))
    (org-redisplay-inline-images))
   ("C-c C-p"
    (org-fitness-call-src-blocks '(modular-plotting-hp))
    (org-redisplay-inline-images))
   ("C-c C-w"
    (ap/capture-workout-data prefix)
    (org-fitness-call-src-blocks '(modular-plotting-hp))
    (org-redisplay-inline-images))
   ("C-c C-s"
    (if (region-active-p)
        (org-fitness-summarize-food-table-lines)
      (unless (org-at-table-p)
        (org-fitness-goto-table "food-log")
        (org-table-goto-line 2))
      (org-fitness-summarize-food-for-day (org-fitness-timestamp-at-point))))
   ))

(require 'ht)
(solarized-with-color-variables
  'dark
  (defconst org-fitness-calories-color yellow)
  (defconst org-fitness-protein-color violet)
  (defconst org-fitness-cost-color green)
  (defconst org-fitness-colors-ht
    (ht
     ("Calories" yellow)
     ("Protein" violet)
     ("Price" green)
     ("Cost" green))))



(defhydra hydra-org-fitness (:color red :hint nil)
  "
       Summarize data for day:
       ^^^^-----------------------
       _c_urrent
       _p_revious
       _n_ext               _q_uit
       "
  ("c" (org-fitness-summarize-food-for-day))
  ;; ("i" (message "%s: %s" (org-fitness-column-name-at-point) (org-fitness-sum-column-for-date)))
  ("n" (progn
         (org-fitness-goto-table "food-log")
         (re-search-forward (rx bol "|-"))
         (forward-line 1)
         (org-fitness-summarize-food-for-day)))
  ("p" (progn
         (org-fitness-goto-table "food-log")
         (re-search-backward (rx bol "|-"))
         (forward-line -1)
         (org-fitness-summarize-food-for-day)))
  ("q" nil))

       ;;; Set faces

;; Set the org-date face to monospace in this file, so the table columns will line up.
(face-remap-add-relative 'org-date :background "#073642" :family (face-attribute 'default :family))
(face-remap-add-relative 'org-formula :background "#073642" :family (face-attribute 'default :family))
(face-remap-add-relative 'org-table :background "#073642")

       ;;; Misc

(require 'ob-table)
(toggle-truncate-lines 1)

;; Run Python blocks without prompting
(unless (member '(python . t) org-babel-load-languages)
  (add-to-list 'org-babel-load-languages '(python . t))
  (org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages))
;; The fitness.org file calls this function to eval the configuration source
;; block. This is the only function that needs to be defined outside of
;; fitness.org.
(defun ap/org-call-src-block (name)
;; Based on <http://kitchingroup.cheme.cmu.edu/blog/2014/08/11/Using-org-mode-outside-of-Emacs-sort-of/>
;; This works better than the org-sbe (aka sbe) macro, because it
;; calls the block upon expansion, making it difficult to bind to
;; a command to run later
;; TODO: Use `org-babel-goto-named-src-block'! I guess it's new...or not, it's from 2010!
(org-with-wide-buffer
(-when-let (src (org-element-map (org-element-parse-buffer) 'src-block
(lambda (element)
(when (string= name (org-element-property :name element))
element))
nil ;info
t ))
(goto-char (org-element-property :begin src))
(let ((org-confirm-babel-evaluate nil))
(org-babel-execute-src-block)))))
;; These go in `org-capture-templates' to capture weight entries.
("f" "Fitness")
("fw" "Weight" table-line
(id "7c721aac-eafa-4a42-9354-fbc151402510")
"| | %U | %^{Weight} | %^{Comment}" :immediate-finish t)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment