- [ ] Install and use ob-async
- [ ] Move as many settings and colors as possible to a stylesheet/rc-file, see http://matplotlib.org/users/customizing.html
! | Date | Food | Calories | Protein | Price | Comment |
---|---|---|---|---|---|---|
[2018-07-19 Wed] | Chef Boyardee: Lasagna (1 can) | 460 | 18 | 1.00 | ||
[2018-07-19 Wed] | Chef Boyardee: Double-stuffed beef ravioli (1 can) | 460 | 20 | 1.00 | ||
[2018-07-18 Tue] | Hardee’s: Natural-Cut French Fries (small) | 300 | 4 | 0.00 | ||
[2018-07-18 Tue] | Hardee’s: Cherry Coke (medium) | 260 | 0 | 0.00 | ||
[2018-07-18 Tue] | Hardee’s: Baby Back Rib Thickburger (1/3 lb.) | 1040 | 47 | 9.29 | ||
[2018-07-18 Tue] | Kroger: spreadable butter w/canola oil (0.5 tbsp) | 50 | 0 | 0.00 | ||
[2018-07-18 Tue] | Kroger: apricot preserves (1 tbsp) | 40 | 0 | 0.00 | ||
[2018-07-18 Tue] | Thomas: blueberry bagel | 270 | 9 | 0.67 | ||
[2018-07-18 Tue] | Kroger: cheddar cheese (1 slice) | 80 | 5 | 0.20 | ||
[2018-07-18 Tue] | Kroger: canned pulled pork (1/2 of 10 oz. can) | 105 | 21 | 1.33 | ||
[2018-07-18 Tue] | Ballpark: Park’s Finest beef frank | 160 | 6 | 0.00 | ||
[2018-07-18 Tue] | Nature’s Own: whole wheat hot dog bun | 110 | 5 | 0.40 | ||
[2018-07-18 Tue] | Nature’s Own: honey wheat bread (2 slices) | 140 | 6 | 0.32 | ||
[2018-07-17 Mon] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-07-17 Mon] | Chick-Fil-A: Sweet Tea (large) | 170 | 0 | 0.00 | ||
[2018-07-17 Mon] | Chick-Fil-A: Classic Chicken Sandwich | 440 | 28 | 0.00 | ||
[2018-07-17 Mon] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-07-17 Mon] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-07-17 Mon] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.25 | ||
[2018-07-16 Sun] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-07-16 Sun] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-07-16 Sun] | Wendy’s: Fries (small) | 320 | 5 | 0.00 | ||
[2018-07-16 Sun] | Wendy’s: Baconator (no ketchup) | 940 | 59 | 8.59 | ||
[2018-07-16 Sun] | Kroger: spreadable butter w/canola oil (0.5 tbsp) | 50 | 0 | 0.00 | ||
[2018-07-16 Sun] | Kroger: apricot preserves (1 tbsp) | 40 | 0 | 0.00 | ||
[2018-07-16 Sun] | Thomas: blueberry bagel | 270 | 9 | 0.67 | ||
[2018-07-16 Sun] | Kroger: cheddar cheese (1 slice) | 80 | 5 | 0.20 | ||
[2018-07-16 Sun] | Kroger: canned pulled pork (1/2 of 10 oz. can) | 105 | 21 | 1.33 | ||
[2018-07-16 Sun] | Sara Lee: Sweet Hawaiian Sandwich Roll (1 bun) | 170 | 5 | 0.34 | ||
[2018-07-15 Sat] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-07-15 Sat] | Arby’s: Mozzarella Sticks (2 piece) | 220 | 9.5 | 1.30 | ||
[2018-07-15 Sat] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-07-15 Sat] | Arby’s: Triple Chocolate Cookie | 450 | 5 | 1.00 | ||
[2018-07-15 Sat] | Arby’s: Salted Caramel & Chocolate Cookie | 430 | 4 | 1.00 | ||
[2018-07-14 Fri] | Ballpark: Park’s Finest beef frank | 160 | 6 | 0.00 | ||
[2018-07-14 Fri] | Ballpark: Park’s Finest beef frank | 160 | 6 | 0.00 | ||
[2018-07-14 Fri] | Nature’s Own: whole wheat hot dog bun | 110 | 5 | 0.40 | ||
[2018-07-14 Fri] | Nature’s Own: whole wheat hot dog bun | 110 | 5 | 0.40 | ||
[2018-07-14 Fri] | Kroger: spreadable butter w/canola oil (0.5 tbsp) | 50 | 0 | 0.00 | ||
[2018-07-14 Fri] | Kroger: apricot preserves (1 tbsp) | 40 | 0 | 0.00 | ||
[2018-07-14 Fri] | Thomas: blueberry bagel | 270 | 9 | 0.67 | ||
[2018-07-13 Thu] | Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan) | 290 | 3 | 0.78 | ||
[2018-07-13 Thu] | Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan) | 290 | 3 | 0.78 | ||
[2018-07-13 Thu] | Wendy’s: Fries (small) | 320 | 5 | 0.00 | ||
[2018-07-13 Thu] | Wendy’s: Baconator (no ketchup) | 940 | 59 | 8.59 | ||
[2018-07-13 Thu] | Wendy’s: Dr. Pepper (medium) | 190 | 0 | 0.00 | ||
[2018-07-13 Thu] | Chick-Fil-A: Chick-n-Minis (3 count) | 270 | 14 | 2.35 | ||
[2018-07-13 Thu] | Chick-Fil-A: Hash Rounds (1 box) | 240 | 2 | 1.19 | ||
[2018-07-13 Thu] | Chick-Fil-A: Spicy Chicken Breakfast Burrito | 450 | 30 | 3.90 | ||
[2018-07-13 Thu] | Chick-Fil-A: Dr. Pepper (medium) | 180 | 0 | 1.59 | ||
[2018-07-12 Wed] | Tai Pei: Pepper Beef (1 package, 283g) | 420 | 13 | 2.50 | ||
[2018-07-12 Wed] | Kroger: spreadable butter w/canola oil (0.5 tbsp) | 50 | 0 | 0.00 | ||
[2018-07-12 Wed] | Kroger: apricot preserves (1 tbsp) | 40 | 0 | 0.00 | ||
[2018-07-12 Wed] | Thomas: blueberry bagel | 270 | 9 | 0.67 | ||
[2018-07-12 Wed] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-07-12 Wed] | Arby’s: Mozzarella Sticks (2 piece) | 220 | 9.5 | 1.30 | ||
[2018-07-12 Wed] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-07-11 Tue] | Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan) | 290 | 3 | 0.78 | ||
[2018-07-11 Tue] | Pizza Hut: Hershey’s Hot Chocolate Brownie (1 square, 1/9th pan) | 290 | 3 | 0.78 | ||
[2018-07-11 Tue] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-07-11 Tue] | Hardee’s: Natural-Cut French Fries (small) | 300 | 4 | 0.00 | ||
[2018-07-11 Tue] | Hardee’s: 1/3 lb. Frisco Thickburger | 840 | 43 | 5.00 | ||
[2018-07-10 Mon] | Papa John’s: Large pizza, extra sauce, 2x pepperoni (1 slice) | 350 | 11 | 0.00 | ||
[2018-07-10 Mon] | Papa John’s: Large pizza, extra sauce, 2x pepperoni (1 slice) | 350 | 11 | 0.00 | ||
[2018-07-10 Mon] | Wendy’s: Bacon Queso Chicken Sandwich | 590 | 37 | 5.49 | ||
[2018-07-10 Mon] | Wendy’s: Strawberry Mango Chicken Salad (w/2 dressing packets) | 470 | 39 | 6.89 | ||
[2018-07-09 Sun] | Arby’s: Triple Chocolate Cookie | 450 | 5 | 1.00 | ||
[2018-07-09 Sun] | Arby’s: Salted Caramel & Chocolate Cookie | 430 | 4 | 1.00 | ||
[2018-07-09 Sun] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-07-09 Sun] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-07-09 Sun] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-07-09 Sun] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-07-08 Sat] | Wendy’s: Fries (small) | 320 | 5 | 0.00 | ||
[2018-07-08 Sat] | Wendy’s: Baconator (no ketchup) | 940 | 59 | 8.59 | ||
[2018-07-08 Sat] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-07-08 Sat] | Chick-Fil-A: Greek yogurt parfait | 230 | 12 | 3.06 | ||
[2018-07-08 Sat] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-07-08 Sat] | Chick-Fil-A: Sweet Tea (large) | 170 | 0 | 0.00 | ||
[2018-07-08 Sat] | Chick-Fil-A: Spicy Chicken Sandwich | 490 | 30 | 3.35 | ||
[2018-07-08 Sat] | Chick-Fil-A: Waffle Potato Fries (large) | 400 | 5 | 1.89 | ||
[2018-07-07 Fri] | Wendy’s: Bacon Queso Chicken Sandwich | 590 | 37 | 8.77 | ||
[2018-07-07 Fri] | Wendy’s: Fries (small) | 320 | 5 | 0.00 | ||
[2018-07-07 Fri] | Wendy’s: Dr. Pepper (large) | 240 | 0 | 0.55 | ||
[2018-07-07 Fri] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-07-07 Fri] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-07-07 Fri] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-07-06 Thu] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-07-06 Thu] | Wendy’s: Fries (small) | 320 | 5 | 0.00 | ||
[2018-07-06 Thu] | Wendy’s: Baconator (no ketchup) | 940 | 59 | 8.59 | ||
[2018-07-06 Thu] | Zaxby’s: Chocolate Chip Cookie | 170 | 2 | 0.00 | ||
[2018-07-06 Thu] | Zaxby’s: Chocolate Chip Cookie | 170 | 2 | 0.00 | ||
[2018-07-05 Wed] | Zaxby’s: Chicken Finger (1 finger) | 100 | 11 | 0.00 | ||
[2018-07-05 Wed] | Zaxby’s: Chicken Finger (1 finger) | 100 | 11 | 0.00 | ||
[2018-07-05 Wed] | Zaxby’s: Crinkle Fries (w/sandwich meal) | 370 | 4 | 0.00 | ||
[2018-07-05 Wed] | Zaxby’s: Texas Toast (1 piece) | 150 | 3 | 0.00 | ||
[2018-07-05 Wed] | Zaxby’s: Sweet Tea (small) | 270 | 0 | 0.00 | ||
[2018-07-05 Wed] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-07-05 Wed] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-07-04 Tue] | Zaxby’s: Crinkle Fries (w/sandwich meal) | 370 | 4 | 0.00 | ||
[2018-07-04 Tue] | Zaxby’s: Honey Mustard (1 portion cup) | 330 | 1 | 0.00 | ||
[2018-07-04 Tue] | Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal) | 230 | 26 | 0.00 | ||
[2018-07-04 Tue] | Zaxby’s: Sweet Tea (large) | 510 | 0 | 0.00 | ||
[2018-07-04 Tue] | Zaxby’s: Brownie | 360 | 4 | 0.99 | ||
[2018-07-04 Tue] | Chick-Fil-A: Chick-n-Minis (4 count) | 350 | 19 | 3.09 | ||
[2018-07-04 Tue] | Chick-Fil-A: Hash Rounds (1 box) | 240 | 2 | 1.19 | ||
[2018-07-04 Tue] | Chick-Fil-A: Spicy Chicken Breakfast Burrito | 450 | 30 | 3.90 | ||
[2018-07-04 Tue] | Chick-Fil-A: Dr. Pepper (large) | 260 | 0 | 1.89 | ||
[2018-07-03 Mon] | Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice) | 305 | 11 | 0.00 | ||
[2018-07-03 Mon] | Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice) | 305 | 11 | 0.00 | ||
[2018-07-03 Mon] | Kroger: spreadable butter w/canola oil (0.5 tbsp) | 50 | 0 | 0.00 | ||
[2018-07-03 Mon] | Quaker: Steel Cut oatmeal w/blueberries, cranberries (1 packet) | 170 | 4 | 0.34 | ||
[2018-07-03 Mon] | Thomas: English muffin, blueberry | 160 | 5 | 0.61 | ||
[2018-07-03 Mon] | Kroger: apricot preserves (1 tbsp) | 40 | 0 | 0.00 | ||
[2018-07-02 Sun] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-02 Sun] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-02 Sun] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-02 Sun] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-02 Sun] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-02 Sun] | Dr. Pepper: from 2-liter bottle (12 oz/355 ml) | 150 | 0 | 0.00 | ||
[2018-07-02 Sun] | Dr. Pepper: from 2-liter bottle (12 oz/355 ml) | 150 | 0 | 0.00 | ||
[2018-07-02 Sun] | Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece) | 170 | 7 | 0.00 | ||
[2018-07-02 Sun] | Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece) | 170 | 7 | 0.00 | ||
[2018-07-02 Sun] | Domino’s: Ultimate Pepperoni (1 slice, pan, 12”) | 340 | 13 | 1.25 | ||
[2018-07-02 Sun] | Domino’s: Ultimate Pepperoni (1 slice, pan, 12”) | 340 | 13 | 1.25 | ||
[2018-07-02 Sun] | Dr. Pepper: from 2-liter bottle (12 oz/355 ml) | 150 | 0 | 0.00 | ||
[2018-07-02 Sun] | Dr. Pepper: from 2-liter bottle (12 oz/355 ml) | 150 | 0 | 0.00 | ||
[2018-07-01 Sat] | Dr. Pepper: from 2-liter bottle (12 oz/355 ml) | 150 | 0 | 0.00 | ||
[2018-07-01 Sat] | Dr. Pepper: from 2-liter bottle (12 oz/355 ml) | 150 | 0 | 0.00 | ||
[2018-07-01 Sat] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-01 Sat] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-01 Sat] | Domino’s: Cinnamon Bread Twist (1 piece) | 250 | 5 | 0 | ||
[2018-07-01 Sat] | Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece) | 170 | 7 | 0 | ||
[2018-07-01 Sat] | Domino’s: Stuffed Cheesy Bread, Bacon & Jalapeno (1 piece) | 170 | 7 | 0 | ||
[2018-07-01 Sat] | Domino’s: Ultimate Pepperoni (1 slice, pan, 12”) | 340 | 13 | 1.25 | ||
[2018-07-01 Sat] | Domino’s: Ultimate Pepperoni (1 slice, pan, 12”) | 340 | 13 | 1.25 | ||
[2018-07-01 Sat] | Domino’s: Ultimate Pepperoni (1 slice, pan, 12”) | 340 | 13 | 1.25 | ||
[2018-06-30 Fri] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-30 Fri] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-30 Fri] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-06-29 Thu] | Hardee’s: Cherry Coke (medium) | 260 | 0 | 0.00 | ||
[2018-06-29 Thu] | Hardee’s: Natural-Cut French Fries (small) | 360 | 4 | 0.00 | ||
[2018-06-29 Thu] | Hardee’s: Baby Back Rib Thickburger (1/3 lb.) | 1040 | 47 | 9.29 | ||
[2018-06-29 Thu] | Nature Valley: Crunchy Oats & Dark Chocolate granola bar | 190 | 3 | 0.50 | ||
[2018-06-29 Thu] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-29 Thu] | Nature Valley: Protein Chewy Bar, peanut butter dark chocolate | 190 | 10 | 0.70 | ||
[2018-06-29 Thu] | Nature Valley: Dark Chocolate, Peanut & Almond granola bar | 160 | 3 | 0.50 | ||
[2018-06-28 Wed] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-28 Wed] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-28 Wed] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 0.00 | ||
[2018-06-27 Tue] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-27 Tue] | Nature Valley: Protein Chewy Bar, peanut butter dark chocolate | 190 | 10 | 0.70 | ||
[2018-06-27 Tue] | Nature Valley: Dark Chocolate, Peanut & Almond granola bar | 160 | 3 | 0.50 | ||
[2018-06-27 Tue] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-06-27 Tue] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-27 Tue] | Arby’s: Triple-Thick Brown Sugar Bacon 1/2-lb. Club sandwich | 830 | 58 | 8.29 | ||
[2018-06-26 Mon] | Nature Valley: Dark Chocolate, Peanut & Almond granola bar | 160 | 3 | 0.50 | ||
[2018-06-26 Mon] | Sonic: Pretzel Dog, Original | 340 | 12 | 2.29 | ||
[2018-06-26 Mon] | Sonic: Pretzel Dog, Original | 340 | 12 | 2.29 | ||
[2018-06-26 Mon] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-06-26 Mon] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-26 Mon] | Arby’s: Double Roast Beef sandwich | 510 | 38 | 4.69 | ||
[2018-06-25 Sun] | Wendy’s: Fries (small) | 320 | 5 | 0.00 | ||
[2018-06-25 Sun] | Wendy’s: Baconator (no ketchup) | 940 | 59 | 8.59 | ||
[2018-06-25 Sun] | Wendy’s: Coca-Cola (small) | 200 | 0 | 0.00 | ||
[2018-06-25 Sun] | Zaxby’s: Crinkle Fries (w/sandwich meal) | 370 | 4 | 0.00 | ||
[2018-06-25 Sun] | Zaxby’s: Honey Mustard (1 portion cup) | 330 | 1 | 0.00 | ||
[2018-06-25 Sun] | Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal) | 230 | 26 | 0.00 | ||
[2018-06-25 Sun] | Zaxby’s: Sweet Tea (large) | 510 | 0 | 0.00 | ||
[2018-06-25 Sun] | Zaxby’s: Texas Toast (1 piece) | 150 | 3 | 0.00 | ||
[2018-06-24 Sat] | Zaxby’s: Crinkle Fries (w/sandwich meal) | 370 | 4 | 0.00 | ||
[2018-06-24 Sat] | Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal) | 230 | 26 | 0.00 | ||
[2018-06-24 Sat] | Zaxby’s: Honey Mustard (1 portion cup) | 330 | 1 | 0.00 | ||
[2018-06-24 Sat] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-06-24 Sat] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-24 Sat] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-24 Sat] | Arby’s: Half-Pound Roast Beef sandwich | 610 | 48 | 5.69 | ||
[2018-06-23 Fri] | Zaxby’s: Sweet Tea (small, 22 oz) | 270 | 0 | 0.00 | ||
[2018-06-23 Fri] | Zaxby’s: Crinkle Fries (w/sandwich meal) | 370 | 4 | 0.00 | ||
[2018-06-23 Fri] | Zaxby’s: Honey Mustard (1 portion cup) | 330 | 1 | 0.00 | ||
[2018-06-23 Fri] | Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal) | 230 | 26 | 6.59 | ||
[2018-06-23 Fri] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-06-23 Fri] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-23 Fri] | Arby’s: Smoke Mountain sandwich | 820 | 44 | 0.00 | ||
[2018-06-22 Thu] | Campbell’s Chunky: Chicken Corn Chowder (1 can, 240ml) | 380 | 10 | 1.79 | ||
[2018-06-22 Thu] | Nature Valley: Dark Chocolate, Peanut & Almond granola bar | 160 | 3 | 0.50 | ||
[2018-06-22 Thu] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-22 Thu] | Chick-Fil-A: Classic Chicken Sandwich | 440 | 28 | 3.09 | ||
[2018-06-22 Thu] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-06-22 Thu] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-06-22 Thu] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-06-22 Thu] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.25 | ||
[2018-06-21 Wed] | Nature Valley: Protein Chewy Bar, peanut butter dark chocolate | 190 | 10 | 0.70 | ||
[2018-06-21 Wed] | Pepsi (~20 oz) | 250 | 0 | 0.00 | ||
[2018-06-21 Wed] | Campbell’s Chunky: Chili, Hot & Spicy with Bean Firehouse (1 can) | 480 | 28 | 1.79 | ||
[2018-06-21 Wed] | Chick-Fil-A: Sweet Tea (large) | 170 | 0 | 0.00 | ||
[2018-06-21 Wed] | Chick-Fil-A: Classic Chicken Sandwich | 440 | 28 | 3.09 | ||
[2018-06-21 Wed] | Chick-Fil-A: Waffle Potato Fries (medium) | 400 | 5 | 1.69 | ||
[2018-06-20 Tue] | Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice) | 360 | 15 | 2.15 | ||
[2018-06-20 Tue] | Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice) | 360 | 15 | 2.15 | ||
[2018-06-20 Tue] | Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice) | 360 | 15 | 2.15 | ||
[2018-06-20 Tue] | Zaxby’s: Sweet Tea (large) | 510 | 0 | 0.50 | ||
[2018-06-20 Tue] | Zaxby’s: Crinkle Fries (w/sandwich meal) | 370 | 4 | 0 | ||
[2018-06-20 Tue] | Zaxby’s: Chicken Finger Sandwich | 820 | 42 | 6.99 | ||
[2018-06-19 Mon] | Nature Valley: Protein Chewy Bar, peanut butter dark chocolate | 190 | 10 | 0.70 | ||
[2018-06-19 Mon] | Campbell’s Chunky: Chicken Corn Chowder (1 can, 240ml) | 380 | 10 | 1.79 | ||
[2018-06-19 Mon] | Chick-Fil-A: Chicken Salad Sandwich | 500 | 27 | 4.09 | ||
[2018-06-19 Mon] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-06-19 Mon] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-06-19 Mon] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-06-19 Mon] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.25 | ||
[2018-06-19 Mon] | Quaker: Steel Cut oatmeal w/blueberries, cranberries (1 packet) | 170 | 4 | 0.34 | ||
[2018-06-18 Sun] | Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice) | 360 | 15 | 2.15 | ||
[2018-06-18 Sun] | Pizza Hut: Lrg Stuffed Crust, 2x pepperoni, pretzel crust (1 slice) | 360 | 15 | 2.15 | ||
[2018-06-18 Sun] | Arby’s: Triple Chocolate Cookie | 450 | 5 | 1.00 | ||
[2018-06-18 Sun] | Arby’s: Sweet Tea (small) | 100 | 0 | 0.00 | ||
[2018-06-18 Sun] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-18 Sun] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-18 Sun] | Arby’s: Double Roast Beef sandwich | 510 | 38 | 4.69 | ||
[2018-06-17 Sat] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-17 Sat] | Nature Valley: Dark Chocolate, Peanut & Almond granola bar | 160 | 3 | 0.50 | ||
[2018-06-17 Sat] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-17 Sat] | Pizza Hut: Meaty Marinara Bake w/garlic bread (individual, 1/2) | 360 | 16.5 | 0.00 | ||
[2018-06-17 Sat] | Chick-Fil-A: Chick-n-Minis (4 count) | 350 | 19 | 3.09 | ||
[2018-06-17 Sat] | Chick-Fil-A: Hash Rounds (1 box) | 240 | 2 | 1.19 | ||
[2018-06-17 Sat] | Chick-Fil-A: Spicy Chicken Breakfast Burrito | 450 | 30 | 3.90 | ||
[2018-06-17 Sat] | Chick-Fil-A: Sweet Tea (large) | 170 | 0 | 0.00 | ||
[2018-06-15 Thu] | Nature Valley: Dark Chocolate, Peanut & Almond granola bar | 160 | 3 | 0.50 | ||
[2018-06-15 Thu] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-15 Thu] | Chick-Fil-A: Sweet Tea (large) | 170 | 0 | 0.00 | ||
[2018-06-15 Thu] | Chick-Fil-A: Chick-n-Minis (4 count) | 350 | 19 | 3.09 | ||
[2018-06-15 Thu] | Chick-Fil-A: Hash Rounds (1 box) | 240 | 2 | 1.19 | ||
[2018-06-15 Thu] | Chick-Fil-A: Spicy Chicken Breakfast Burrito | 450 | 30 | 3.90 | ||
[2018-06-08 Thu] | Chick-Fil-A: Fruit Cup (medium) | 45 | 0 | 2.75 | ||
[2018-06-08 Thu] | Chick-Fil-A: Grilled Chicken Cool Wrap | 350 | 37 | 5.25 | ||
[2018-06-08 Thu] | Chick-Fil-A: Dr. Pepper (medium) | 180 | 0 | 1.59 | ||
[2018-06-08 Thu] | Nature Valley: Protein Chewy Bar, peanut butter dark chocolate | 190 | 10 | 0.70 | ||
[2018-06-08 Thu] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-07 Wed] | Kellogg’s: Special K Protein Meal Bar, Chocolately Chip | 170 | 12 | 1.00 | ||
[2018-06-07 Wed] | Maruchan: Ramen noodles, beef (1 package with flavor packet) | 380 | 10 | 0.21 | ||
[2018-06-07 Wed] | Campbell’s: Slow Kettle Style Soup, SW-Style Chicken Chili (15.7 oz) | 380 | 26 | 2.98 | ||
[2018-06-07 Wed] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.25 | ||
[2018-06-07 Wed] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-06-07 Wed] | Chick-Fil-A: Nuggets (8 ct.) | 270 | 28 | 3.09 | ||
[2018-06-07 Wed] | Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice) | 305 | 11 | 0.00 | ||
[2018-06-07 Wed] | Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice) | 305 | 11 | 0.00 | ||
[2018-06-06 Tue] | Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice) | 305 | 11 | 0.00 | ||
[2018-06-06 Tue] | Papa John’s: Pan pizza, 2x pepperoni, extra pan sauce (1 slice) | 305 | 11 | 0.00 | ||
[2018-06-06 Tue] | Arby’s: Three-Cheese Steak sandwich | 650 | 42 | 7.89 | ||
[2018-06-06 Tue] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 1.30 | ||
[2018-06-06 Tue] | Arby’s: Mt. Dew (small) | 200 | 0 | 0.00 | ||
[2018-06-05 Mon] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 2.99 | ||
[2018-06-05 Mon] | Arby’s: Double Roast Beef sandwich | 510 | 38 | 4.69 | ||
[2018-06-05 Mon] | Sonic: Sweet Tea (Rt. 44) | 360 | 0 | 0.00 | ||
[2018-06-04 Sun] | Arby’s: Triple Chocolate Cookie | 450 | 5 | 1.00 | ||
[2018-06-04 Sun] | Papa John’s: Bacon Cheddarburger pan pizza, 1 slice | 330 | 12 | 0.00 | ||
[2018-06-04 Sun] | Papa John’s: Bacon Cheddarburger pan pizza, 1 slice | 330 | 12 | 0.00 | ||
[2018-06-04 Sun] | Papa John’s: Bacon Cheddarburger pan pizza, 1 slice | 330 | 12 | 0.00 | ||
[2018-06-04 Sun] | Arby’s: Salted Caramel & Chocolate Cookie | 430 | 4 | 1.00 | ||
[2018-06-04 Sun] | Arby’s: Dr. Pepper (small) | 130 | 0 | 1.59 | ||
[2018-06-04 Sun] | Arby’s: Half-Pound Roast Beef sandwich | 610 | 48 | 5.69 | ||
[2018-06-04 Sun] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 2.99 | ||
[2018-06-03 Sat] | Sonic: Custard Concrete, Dark Chocolate & Oreo (2/3 of small) | 590 | 9 | 5.47 | ||
[2018-06-03 Sat] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-03 Sat] | Dickey’s: green beans w/bacon (4 oz. side) | 84 | 3 | 2.25 | ||
[2018-06-03 Sat] | Dickey’s: waffle fries (4 oz. side) | 338 | 4 | 2.25 | ||
[2018-06-03 Sat] | Dickey’s: Westerner w/chopped brisket, pulled pork (1 sandwich) | 868 | 43.5 | 8.50 | ||
[2018-06-02 Fri] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.42 | ||
[2018-06-02 Fri] | Papa John’s: Bacon Cheddarburger pan pizza, 1 slice | 330 | 12 | 0.00 | ||
[2018-06-02 Fri] | Papa John’s: Bacon Cheddarburger pan pizza, 1 slice | 330 | 12 | 0.00 | ||
[2018-06-02 Fri] | Arby’s: Mozzarella Sticks (4 piece) | 440 | 19 | 2.99 | ||
[2018-06-02 Fri] | Arby’s: Double Roast Beef sandwich | 510 | 38 | 4.69 | ||
[2018-06-01 Thu] | Pepperidge Farm: Swirl caramel apple bread (1 slice) | 100 | 3 | 0.29 | ||
[2018-06-01 Thu] | Pepperidge Farm: Swirl caramel apple bread (1 slice) | 100 | 3 | 0.29 | ||
[2018-06-01 Thu] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.75 | ||
[2018-06-01 Thu] | KFC: Chicken Pot Pie | 790 | 29 | 4.99 | ||
[2018-06-01 Thu] | Chick-Fil-A: Sweet Tea (large) | 170 | 0 | 0.00 | ||
[2018-06-01 Thu] | Chick-Fil-A: Chick-n-Minis (4 count) | 350 | 19 | 3.09 | ||
[2018-06-01 Thu] | Chick-Fil-A: Hash Rounds (1 box) | 240 | 2 | 1.19 | ||
[2018-06-01 Thu] | Chick-Fil-A: Spicy Chicken Breakfast Burrito | 450 | 30 | 3.90 | ||
[2018-04-27 Thu] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 1.75 | ||
[2018-04-27 Thu] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.25 | ||
[2018-04-27 Thu] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-04-27 Thu] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-04-27 Thu] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-04-18 Tue] | Sonic: Chili Cheese Coney (regular) | 500 | 20 | 0.00 | ||
[2018-04-18 Tue] | Sonic: Tater Tots (small) | 250 | 2 | 0.00 | ||
[2018-04-18 Tue] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.25 | ||
[2018-04-18 Tue] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-04-18 Tue] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-04-18 Tue] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-04-18 Tue] | Chick-Fil-A: Spicy Chicken Sandwich | 490 | 30 | 3.35 | ||
[2018-04-18 Tue] | Sonic: Dr. Pepper w/cranberry, vanilla (large) | 325 | 0 | 2.89 | ||
[2018-04-07 Fri] | Chick-Fil-A: Hash Rounds (1 box) | 240 | 2 | 1.19 | ||
[2018-04-07 Fri] | Chick-Fil-A: Fruit Cup (small) | 45 | 2.09 | 2.09 | ||
[2018-04-07 Fri] | Chick-Fil-A: Spicy Chicken Breakfast Burrito | 450 | 30 | 3.90 | ||
[2018-03-28 Tue] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.19 | ||
[2018-03-28 Tue] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-03-28 Tue] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-03-28 Tue] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
[2018-03-28 Tue] | Chick-Fil-A: Spicy Chicken Sandwich | 490 | 30 | 3.35 | ||
[2018-03-27 Mon] | Chick-Fil-A: Chicken Soup (small) | 140 | 12 | 2.69 | ||
[2018-03-27 Mon] | Nature’s Own: whole wheat hot dog bun | 110 | 5 | 0.40 | ||
[2018-03-27 Mon] | McCormick: Grill Mates, Montreal Steak smoked sausage (1 link) | 180 | 9 | 0.37 | ||
[2018-03-27 Mon] | Nature’s Own: whole wheat hot dog bun | 110 | 5 | 0.40 | ||
[2018-03-27 Mon] | Ballpark: Angus Beef Frank (original) | 150 | 6 | 0.62 | ||
[2018-03-27 Mon] | Chick-Fil-A: Grilled Market Salad | 320 | 26 | 7.19 | ||
[2018-03-27 Mon] | Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 1 | 0.00 | ||
[2018-03-27 Mon] | Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 1 | 0.00 | ||
[2018-03-27 Mon] | Chick-Fil-A: Zesty Apple Cider Vinaigrette Dressing (1 packet) | 200 | 0 | 0.00 | ||
<57> |
[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.
! | Date | Goal |
---|---|---|
[2018-06-27 Tue] | 1836 | |
[2017-09-23 Fri] | 1910 | |
[2017-08-23 Tue] | 1910 |
! | Date | Movement | Reps | Type | Comments |
---|---|---|---|---|---|
[2018-07-19 Mon] | Let Me Ins (underhand grip, knees slightly bent) | 24 | Supersets/2nd | ||
[2018-07-19 Mon] | Let Me Ups (knees bent 90 degrees) | 10 | Supersets/1st | ||
[2018-07-19 Mon] | Let Me Ups (knees bent 90 degrees) | 6 | Supersets/2nd | ||
[2018-07-19 Mon] | Let Me Ins (4-second hold; knees bent 90 degrees) | 10 | Supersets/1st | ||
[2018-07-19 Mon] | Let Me Ins (knees bent 90 degrees) | 24 | Supersets/2nd | ||
[2018-07-19 Mon] | Pull-Ups (assisted) | 10 | Supersets/1st | ||
[2018-07-17 Sat] | Good Mornings | 43 | Ladders | ||
[2018-07-17 Sat] | Squats (2-second pause at bottom) | 48 | Ladders | ||
[2018-07-17 Sat] | One-legged Romanian Deadlifts (2-second pause) | 21 | Ladders | ||
[2018-07-17 Sat] | Back Lunges | 31 | Ladders | ||
[2018-07-15 Thu] | Burpees (w/hip-height push-ups) | 32 | Tabatas | ||
[2018-07-15 Thu] | Rocking Chairs (arms extended) | 48 | Tabatas | ||
[2018-07-15 Thu] | Classic Push-Ups (hands hip height) | 56 | Tabatas | ||
[2018-07-13 Tue] | Classic Push-Ups (hands hip height) | 36 | Stappers | ||
[2018-07-13 Tue] | Let Me Ins (knees bent 90 degrees) | 48 | Stappers | ||
[2018-07-13 Tue] | Back Lunges | 70 | Stappers | ||
[2018-07-12 Mon] | Standing Knee Raises | 64 | Tabatas | ||
[2018-07-12 Mon] | Beach Scissors (lying on side) | 48 | Tabatas | ||
[2018-07-12 Mon] | Russian Twists | 88 | Tabatas | ||
[2018-07-10 Sat] | Towel Curls | 36 | Intervals | ||
[2018-07-10 Sat] | Let Me Ins (underhand grip, knees slightly bent) | 36 | Intervals | ||
[2018-07-10 Sat] | Let Me Ups (knees bent 90 degrees) | 17 | Intervals | ||
[2018-07-10 Sat] | Let Me Ins (knees bent 90 degrees) | 36 | Intervals | ||
[2018-07-09 Fri] | Calf Raises (both legs) | 24 | Supersets/2nd | ||
[2018-07-09 Fri] | One-legged Romanian Deadlifts (2-second pause) | 10 | Supersets/1st | ||
[2018-07-09 Fri] | Side Lunges | 24 | Supersets/2nd | ||
[2018-07-09 Fri] | Lunges (4-second pause) | 10 | Supersets/1st | ||
[2018-07-09 Fri] | Toyotas | 18 | Supersets/2nd | ||
[2018-07-09 Fri] | Back Lunges (hands behind head, 4-second pause) | 10 | Supersets/1st | ||
[2018-07-08 Thu] | Seated Dips (knees bent) | 46 | Ladders | ||
[2018-07-08 Thu] | Close-Grip Push-Ups (hands hip height) | 48 | Ladders | ||
[2018-07-08 Thu] | Classic Push-Ups (hands knee height) | 18 | Ladders | ||
[2018-07-08 Thu] | Military Press (hands knee height) | 40 | Ladders | ||
[2018-07-06 Tue] | Leg Lifts (hands under butt) | 24 | Supersets/2nd | ||
[2018-07-06 Tue] | Supermans | 10 | Supersets/1st | ||
[2018-07-06 Tue] | Swimmers (slow) | 24 | Supersets/2nd | ||
[2018-07-06 Tue] | Hyperextensions (hands at side) | 10 | Supersets/1st | ||
[2018-07-06 Tue] | Russian Twists | 24 | Supersets/2nd | ||
[2018-07-06 Tue] | V-Ups | 10 | Supersets/1st | ||
[2018-07-05 Mon] | Let Me Ins (underhand grip, knees slightly bent) | 20 | Supersets/2nd | ||
[2018-07-05 Mon] | Let Me Ups (knees bent 90 degrees) | 5 | Supersets/1st | ||
[2018-07-05 Mon] | Towel Curls | 24 | Supersets/2nd | ||
[2018-07-05 Mon] | Let Me Ins (4-second hold; knees bent 90 degrees) | 10 | Supersets/1st | ||
[2018-07-05 Mon] | Let Me Ins (knees bent 90 degrees) | 24 | Supersets/2nd | ||
[2018-07-05 Mon] | Pull-Ups (assisted) | 10 | Supersets/1st | ||
[2018-07-02 Fri] | Squats (2-second pause at bottom) | 24 | Supersets/2nd | ||
[2018-07-02 Fri] | One-legged Romanian Deadlifts | 10 | Supersets/1st | ||
[2018-07-02 Fri] | Side Lunges | 24 | Supersets/2nd | ||
[2018-07-02 Fri] | Lunges (4-second pause) | 8 | Supersets/1st | ||
[2018-07-02 Fri] | Toyotas | 22 | Supersets/2nd | ||
[2018-07-02 Fri] | Back Lunges (hands behind head, 4-second pause) | 10 | Supersets/1st | ||
[2018-06-30 Wed] | Close-Grip Push-Ups (hands hip height) | 10 | Supersets/1st | ||
[2018-06-30 Wed] | Thumbs Up | 24 | Supersets/2nd | ||
[2018-06-30 Wed] | Military Press (hands knee height) | 10 | Supersets/1st | ||
[2018-06-30 Wed] | Shove Offs (hands hip height) | 24 | Supersets/2nd | ||
[2018-06-30 Wed] | Classic Push-Ups (hands on ground) | 5 | Supersets/1st | ||
[2018-06-29 Tue] | Leg Lifts (hands under butt) | 24 | Supersets/2nd | ||
[2018-06-29 Tue] | Supermans | 10 | Supersets/1st | ||
[2018-06-29 Tue] | Swimmers (slow) | 24 | Supersets/2nd | ||
[2018-06-29 Tue] | Hyperextensions (hands at side) | 10 | Supersets/1st | ||
[2018-06-29 Tue] | Russian Twists | 24 | Supersets/2nd | ||
[2018-06-29 Tue] | V-Ups | 10 | Supersets/1st | ||
[2018-06-22 Tue] | Let Me Ins (underhand grip, knees slightly bent) | 20 | Supersets/2nd | ||
[2018-06-22 Tue] | Let Me Ups (knees bent 90 degrees) | 4 | Supersets/1st | ||
[2018-06-22 Tue] | Towel Curls | 24 | Supersets/2nd | ||
[2018-06-22 Tue] | Let Me Ins (4-second hold; knees bent 90 degrees) | 10 | Supersets/1st | ||
[2018-06-22 Tue] | Let Me Ins (knees bent 90 degrees) | 24 | Supersets/2nd | ||
[2018-06-22 Tue] | Pull-Ups (assisted) | 6 | Supersets/1st | ||
[2018-06-18 Fri] | Squats (2-second pause at bottom) | 24 | Supersets/2nd | ||
[2018-06-18 Fri] | One-legged Romanian Deadlifts | 10 | Supersets/1st | ||
[2018-06-18 Fri] | Side Lunges | 24 | Supersets/2nd | ||
[2018-06-18 Fri] | Lunges | 10 | Supersets/1st | ||
[2018-06-18 Fri] | Toyotas | 18 | Supersets/2nd | ||
[2018-06-18 Fri] | Back Lunges | 10 | Supersets/1st | ||
[2018-06-17 Thu] | Seated Dips (knees bent) | 24 | Supersets/2nd | ||
[2018-06-17 Thu] | Close-Grip Push-Ups (hands hip height) | 10 | Supersets/1st | ||
[2018-06-17 Thu] | Thumbs Up | 24 | Supersets/2nd | ||
[2018-06-17 Thu] | Military Press (hands knee height) | 9 | Supersets/1st | ||
[2018-06-17 Thu] | Shove Offs (hands hip height) | 24 | Supersets/2nd | ||
[2018-06-17 Thu] | Classic Push-Ups (hands on ground) | 5 | Supersets/1st | ||
[2018-06-16 Wed] | Swimmers | 36 | Intervals | ||
[2018-06-16 Wed] | Russian Twists | 36 | Intervals | ||
[2018-06-16 Wed] | Hyperextensions (hands under chin) | 36 | Intervals | ||
[2018-06-16 Wed] | Leg Lifts (hands under butt) | 36 | Intervals | ||
[2018-06-12 Sat] | Towel Curls | 12 | Intervals | ||
[2018-06-12 Sat] | Let Me Ins (underhand grip, knees slightly bent) | 36 | Intervals | ||
[2018-06-12 Sat] | Let Me Ups (knees bent 90 degrees) | 16 | Intervals | ||
[2018-06-12 Sat] | Let Me Ins (knees bent 90 degrees) | 36 | Intervals | ||
[2018-06-10 Thu] | One-legged Romanian Deadlifts | 36 | Intervals | ||
[2018-06-10 Thu] | Squats (2-second pause at bottom) | 36 | Intervals | ||
[2018-06-10 Thu] | Side Lunges | 36 | Intervals | ||
[2018-06-10 Thu] | Bulgarian Split Squats (hands behind head) | 22 | Intervals | ||
[2018-06-07 Mon] | Classic Push-Ups (hands hip height) | 36 | Intervals | ||
[2018-06-07 Mon] | Military Press (hands knee height) | 12 | Intervals | ||
[2018-06-07 Mon] | Close-Grip Push-Ups (hands hip height) | 36 | Intervals | ||
[2018-06-07 Mon] | Seated Dips (legs straight) | 6 | Intervals | ||
[2018-06-06 Sun] | Swimmers | 36 | Intervals | ||
[2018-06-06 Sun] | Russian Twists | 36 | Intervals | ||
[2018-06-06 Sun] | Hyperextensions (hands under chin) | 36 | Intervals | ||
[2018-06-06 Sun] | Leg Lifts (hands under butt) | 36 | Intervals | ||
[2018-06-03 Thu] | Towel Curls | 25 | Intervals | ||
[2018-06-03 Thu] | Let Me Ins (underhand grip, knees slightly bent) | 31 | Intervals | ||
[2018-06-03 Thu] | Let Me Ups (knees bent 90 degrees) | 13 | Intervals | ||
[2018-06-03 Thu] | Let Me Ins (knees bent 90 degrees) | 36 | Intervals | ||
[2018-06-01 Tue] | One-legged Romanian Deadlifts | 34 | Intervals | ||
[2018-06-01 Tue] | Squats (2-second pause at bottom) | 36 | Intervals | ||
[2018-06-01 Tue] | Side Lunges | 36 | Intervals | ||
[2018-06-01 Tue] | Bulgarian Split Squats (hands behind head) | 32 | Intervals | ||
[2018-05-28 Fri] | Classic Push-Ups (hands hip height) | 36 | Intervals | ||
[2018-05-28 Fri] | Military Press (hands knee height) | 20 | Intervals | ||
[2018-05-28 Fri] | Close-Grip Push-Ups (hands hip height) | 14 | Intervals | ||
[2018-05-28 Fri] | Seated Dips (knees bent) | 36 | Intervals | ||
[2018-05-27 Thu] | Russian Twists | 93 | Ladders | ||
[2018-05-27 Thu] | Squats (2-second pause at bottom) | 55 | Ladders | ||
[2018-05-27 Thu] | One-legged Romanian Deadlifts | 36 | Ladders | ||
[2018-05-27 Thu] | Side Lunges | 40 | Ladders | ||
[2018-05-26 Wed] | Let Me Ups (knees bent 90 degrees) | 40 | Ladders | ||
[2018-05-26 Wed] | Seated Dips (knees bent) | 48 | Ladders | ||
[2018-05-26 Wed] | Let Me Ins (knees bent 90 degrees) | 43 | Ladders | ||
[2018-05-26 Wed] | “Let Me Downs (push-ups, hands knee height)” | 21 | Ladders | ||
[2018-05-24 Mon] | Swimmers | 51 | Ladders | ||
[2018-05-24 Mon] | Squats | 58 | Ladders | ||
[2018-05-24 Mon] | One-legged Romanian Deadlifts | 33 | Ladders | ||
[2018-05-24 Mon] | Back Lunges | 28 | Ladders | ||
[2018-05-21 Fri] | Let Me Ups (knees bent 90 degrees) | 33 | Ladders | ||
[2018-05-21 Fri] | Seated Dips (knees bent) | 43 | Ladders | ||
[2018-05-21 Fri] | Let Me Ins (knees bent 90 degrees) | 43 | Ladders | ||
[2018-05-21 Fri] | “Let Me Downs (push-ups, hands knee height)” | 18 | Ladders | ||
[2018-05-19 Wed] | Russian Twists | 85 | Ladders | ||
[2018-05-19 Wed] | Squats (2-second pause at bottom) | 46 | Ladders | ||
[2018-05-19 Wed] | One-legged Romanian Deadlifts | 30 | Ladders | ||
[2018-05-19 Wed] | Side Lunges | 36 | Ladders | ||
[2018-05-10 Mon] | “Let Me Downs (push-ups, hands knee height)” | 15 | Ladders | ||
[2018-05-10 Mon] | Let Me Ins (knees bent 90 degrees) | 33 | Ladders | ||
[2018-05-10 Mon] | Seated Dips (knees bent) | 40 | Ladders | ||
[2018-05-10 Mon] | Let Me Ups (knees bent 90 degrees) | 25 | Ladders | ||
[2018-05-04 Tue] | Back Lunges | 25 | Ladders | ||
[2018-05-04 Tue] | One-legged Romanian Deadlifts | 28 | Ladders | ||
[2018-05-04 Tue] | Squats | 40 | Ladders | ||
[2018-05-04 Tue] | Swimmers | 46 | Ladders |
! | Date | Burned | Comment |
---|---|---|---|
[2018-07-01 Sun] | 171 | W3D2 | |
[2018-06-27 Wed] | 171 | W2D4 | |
[2018-06-25 Mon] | 171 | W2D3 | |
[2018-06-24 Sun] | 171 | W2D2 | |
[2018-06-21 Thu] | 171 | W2D1 | |
[2018-06-19 Tue] | 171 | W1D4 | |
[2018-06-16 Sat] | 109 | ||
[2018-06-10 Sun] | 389 | ||
[2018-06-03 Sun] | 389 | ||
[2018-05-28 Mon] | 263 |
! | Date | Weight | Comment |
---|---|---|---|
[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 |
(org-fitness-list-food-by "price")
(org-fitness-list-food-by "calories")
Food | Calories/Protein | Calories | Protein | Price | Comment |
Chicken-of-the-Sea: Canned Salmon (1 5-oz can) | 4 | 120 | 26 | 1.00 | |
Met-Rx: 100% Natural Whey Chocolate (1 scoop) | 5 | 130 | 23 | 0.93 | |
Kroger: canned pulled pork (56g) | 5 | 60 | 12 | 0.76 | |
Kroger: canned pulled pork (1/2 of 10 oz. can) | 5 | 105 | 21 | 1.33 | |
Kroger: Roast Beef, deli sliced (2 oz) | 5 | 60 | 11 | 0.48 | |
Kroger: turkey breast, honey smoked, deli thin sliced (2 oz) | 6 | 60 | 9 | 0.66 | |
Kroger: turkey breast, mesquite smoked, deli thin sliced | 6 | 60 | 9 | 0.66 | |
Kroger: deli fried chicken tender (1 piece, ~125g) | 8 | 308 | 37.5 | 1.66 | |
Zaxby’s: Chicken Fingers (3) (Big Zax Snak Meal) | 8 | 230 | 26 | 6.59 | Price of Snak Meal |
Chick-Fil-A: Nuggets (8 ct.) | 9 | 270 | 28 | 3.09 | |
Chick-Fil-A: Chick-n-Strips (3 count) | 10 | 360 | 33 | 3.39 | |
Chick-Fil-A: Chick-n-Strips (4 count) | 10 | 470 | 43 | 4.39 | |
Chick-Fil-A: Chicken Soup (small) | 11 | 140 | 12 | 2.69 | |
Chick-Fil-A: Chicken Tortilla Soup | 11 | 260 | 22 | 3.65 | |
Chick-Fil-A: Grilled Market Salad | 12 | 320 | 26 | 7.19 | |
Kroger: swiss cheese (1 slice) | 13 | 80 | 6 | 0.31 | |
homemade: hamburger patty (100g, guessing) | 13 | 204 | 15 | 0.00 | from visiting Ms. Dodie |
Arby’s: Roast Beef Mid sandwich | 13 | 460 | 33 | 4.29 | |
Kraft: Philadelphia cream cheese, black cherry (1 tbsp) | 14 | 35 | 2.5 | 0.22 | |
Jimmy Dean: Bacon Breakfast Bowl | 14 | 410 | 28 | 2.49 | |
Kraft: Philadelphia cream cheese, black cherry (2 tbsp) | 14 | 70 | 5 | 0.43 | |
Arby’s: Angus Three Cheese & Bacon sandwich | 14 | 670 | 47 | 6.00 | Meal was actually free since they got yesterday’s wrong. |
Wendy’s: Baconator (no ketchup) | 15 | 940 | 59 | 8.59 | |
Arby’s: Three Cheese Steak sandwich w/bacon (1/2 of it) | 15 | 368 | 24 | 6.60 | Counting full price of it. |
KFC: Nashville Hot Chicken (3 tenders) | 15 | 530 | 34 | 5.49 | Nutrition info from a non-KFC site; price includes sides |
Arby’s: Three Cheese Steak sandwich w/bacon | 15 | 736 | 48 | 6.60 | |
Chick-Fil-A: Classic Chicken Sandwich | 15 | 440 | 28 | 3.09 | |
Arby’s: French Dip & Swiss sandwich | 15 | 540 | 35 | 6.74 | |
Kroger: deli fried chicken tender (1 piece, ~102g) | 16 | 304 | 18 | 1.35 | |
Chick-Fil-A: Spicy Chicken Sandwich | 16 | 490 | 30 | 3.35 | |
Kroger: cheddar cheese (1 slice) | 16 | 80 | 5 | 0.20 | From 4-type multi pack, cheaper than cheddar-only package |
Kroger: Monterey Jack cheese (1 slice) | 16 | 80 | 5 | 0.20 | |
Kroger: deli fried chicken tender (1 piece, 67g) | 16 | 200 | 12 | 0 | Check price |
Kroger: deli fried chicken tender (1 piece, ~107g) | 16 | 319 | 19 | 1.43 | |
Kroger: Colby cheese (1 slice) | 16 | 80 | 5 | 0.20 | |
Hardee’s: Bacon 3-Way, 1/2 lb. (w/o cheese, lettuce, tomato) | 17 | 1020 | 57 | 10.28 | 3 am election celebration (didn’t give me receipt) |
Campbell’s Chunky: Chili, Hot & Spicy with Bean Firehouse (1 can) | 17 | 480 | 28 | 1.79 | |
Chick-Fil-A: Chicken, Egg & Cheese Bagel (1 sandwich) | 17 | 480 | 27 | 3.79 | |
Thomas: 100% Whole Wheat Bagel Thins (1 bagel thin) | 18 | 110 | 6 | 0.00 | Free from Ms. Dodie (someone gave them to her) |
Campbell’s Chunky Soup: Spicy BBQ Seasoned Chicken w/Beans (1 can) | 19 | 420 | 22 | 1.79 | |
Chick-Fil-A: Greek yogurt parfait w/chocolate cookie crumbs | 19 | 210 | 11 | 2.95 | |
McCormick: Grill Mates, Montreal Steak smoked sausage (1 link) | 20 | 180 | 9 | 0.37 | |
Jimmy Dean: Turkey Sausage & Bacon frittatas (2) | 20 | 260 | 13 | 1.96 | |
Chick-Fil-A: Spicy Chicken, Egg, and Cheese Biscuit | 20 | 560 | 28 | 3.19 | |
Campbell’s Chunky: Savory Pot Roast | 20 | 240 | 12 | 1.79 | |
Chick-Fil-A: Chicken, Egg, and Cheese Biscuit | 20 | 560 | 28 | 3.98 | |
Nature’s Own: whole wheat hamburger bun | 21 | 130 | 6 | 0.40 | |
Taco Bell: Steakhouse Burrito, steak (1/2 burrito) | 21 | 400 | 19.5 | 6.19 | Ate half, counted full price |
Hardee’s: Pork Chop’n’Gravy Biscuit | 21 | 590 | 28 | 5.46 | Combo with small drink, small hash browns |
Taco Bell: Cruncy Taco (beef, cheese, lettuce) | 21 | 170 | 8 | 0.00 | Price included in combo |
Wolf Brand: chili, no beans (1 can) | 21 | 800 | 38 | 2.19 | |
Nature’s Own: whole wheat hot dog bun | 22 | 110 | 5 | 0.40 | |
Banquet: Mega Meal, salisbury steak | 22 | 640 | 28 | 1.99 | |
Taco Bell: DoubleDilla, steak, chips & salsa | 22 | 920 | 41 | 5.95 | They ran it up COMPLETELY wrong, so I’ve no idea how much it would actually cost. |
Campbell’s Chunky: Chili Mac (1 can) | 22 | 400 | 18 | 1.79 | |
Nature’s Own: honey wheat bread (2 slices) | 23 | 140 | 6 | 0.32 | |
Arby’s: Mozzarella Sticks (3 piece) | 23 | 345 | 15 | 1.38 | He gave me a 7-piece by mistake, so I ate 4 and now the rest. |
Arby’s: Mozzarella Sticks (4 piece) | 23 | 460 | 20 | 1.84 | |
Stouffer’s: Chicken Fettucini Alfredo | 24 | 540 | 22 | 2.79 | |
Hardee’s: Country Ham Biscuit | 24 | 370 | 15 | 0.00 | |
Quaker: Protein Oatmeal, Cranberry Almond (1 packet) | 24 | 240 | 10 | 0.50 | |
KFC: Chicken Little, Nashville Hot | 24 | 320 | 13 | 1.59 | Don’t know if Nashville Hot makes a difference |
Tai Pei: Pepper Beef (1 package, 403g) | 25 | 490 | 19 | 2.50 | |
Campbell’s Chunky: Chipotle Chicken & Corn Chowder (1 can, 240ml) | 25 | 360 | 14 | 1.79 | |
Kroger: creamy peanut butter (2 tbsp/32 g) | 25 | 180 | 7 | 0.12 | |
Kroger: creamy peanut butter (1 tbsp/16 g) | 26 | 90 | 3.5 | 0.06 | |
Pepperidge Farm: Swirl cinnamon raisin bread (1 slice) | 26 | 80 | 3 | 0.19 | |
Campbell’s Chunky Soup: Chicken, Broccoli, Cheese w/potato (1 can) | 27 | 380 | 14 | 1.79 | |
KFC: Chicken Pot Pie | 27 | 790 | 29 | 5.20 | Price 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 Biscuit | 27 | 440 | 16 | Free one from last time, but they also left off the egg and cheese this time. | |
Taco Bell: Quesarito, beef, (- sour cream, nacho cheese) | 28 | 590 | 21 | 5.95 | |
Taco Bell: Beefy Fritos Burrito (-nacho cheese, w/3 cheese blend) | 28 | 450 | 16 | 1.39 | |
Wal-Mart/Marketside: chicken salad w/cranberries, pecans (1/4 cup) | 30 | 150 | 5 | 0.00 | Check price |
Thomas: blueberry bagel | 30 | 270 | 9 | 0.67 | |
Quaker: Old Fashioned Oatmeal (1/2 cup) | 30 | 150 | 5 | 0.00 | |
Oroweat: sesame-seeded sandwich roll | 30 | 150 | 5 | 0.00 | from visiting Ms. Dodie |
Glutenfreeda: Banana Maple with Flax instant oatmeal | 30 | 180 | 6 | 0.00 | Check price |
Thomas: “Everything” bagel | 31 | 280 | 9 | 0.58 | |
Taco Bell: Steakhouse Queso Nachos, steak (see comments) | 32 | 880 | 27 | 6.19 | No 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) | 32 | 260 | 8 | 0.33 | |
Pepperidge Farm: Swirl caramel apple bread (1 slice) | 33 | 100 | 3 | 0.29 | |
Sara Lee: Sweet Hawaiian Sandwich Roll (1 bun) | 34 | 170 | 5 | 0.34 | |
Kroger: Fresh Foods Market: loaded baked potato style soup (15 oz) | 35 | 640 | 18 | 3.59 | |
Tai Pei: Sweet & Sour Chicken (1 container, 397g) | 35 | 460 | 13 | 2.50 | Blech. Hardly any chicken. Not doing this again. |
Maruchan: Ramen noodles, beef (1 package with flavor packet) | 38 | 380 | 10 | 0.21 | |
Campbell’s Chunky: Chicken Corn Chowder (1 can, 240ml) | 38 | 380 | 10 | 1.79 | |
Kellogg’s: Special K Nourish Apple Raspberry Almond cereal (1 cup) | 38 | 190 | 5 | 0.64 | |
Quaker: Instant Oatmeal, apple cinnamon (1 packet) | 40 | 160 | 4 | 0.00 | |
Quaker: Steel Cut oatmeal w/blueberries, cranberries (1 packet) | 42 | 170 | 4 | 0.34 | Price from Google search, Walmart listing |
KFC: biscuit | 45 | 180 | 4 | 0.00 | |
Chick-Fil-A: Peppermint Chocolate Chip milkshake (1/2 of small) | 49 | 295 | 6 | 1.43 | |
Marie Callender’s: Turkey Pot Pie (1 pie, 16 oz) | 49 | 1090 | 22 | 2.5 | |
Zaxby’s: Texas Toast (1 piece) | 50 | 150 | 3 | 0.00 | Price included in Snak Meal |
Chick-Fil-A: Harvest Nut Granola (1 packet) | 60 | 60 | 1 | 0.00 | |
Taco Bell: Chips and Salsa | 62 | 250 | 4 | 1.29 | |
Wendy’s: Fries (small) | 64 | 320 | 5 | 0.00 | |
KFC: Potato Wedges (individual size) | 67 | 270 | 4 | 0.00 | |
Chick-Fil-A: Seasoned Tortilla Strips | 70 | 70 | 1 | 0.00 | |
Chick-Fil-A: Roasted Nut Blend (1 packet) | 70 | 70 | 1 | 0.00 | |
blueberries (1/4 cup) | 76 | 21.25 | 0.28 | 0.00 | Check price |
Kraft: Philadelphia cream cheese, pumpkin spice (1 tbsp) | 80 | 40 | 0.5 | 0.21 | |
Chick-Fil-A: Waffle Potato Fries (medium) | 80 | 400 | 5 | 1.69 | |
Kraft: Philadelphia cream cheese, strawberry (1 tbsp) | 80 | 40 | 0.5 | 0.21 | |
Chick-Fil-A: Chocolate Chip Cookie | 87 | 350 | 4 | 1.19 | |
Zaxby’s: Crinkle Fries (5 oz) | 88 | 440 | 5 | 0.00 | Price included in Snak Meal |
Arby’s: Triple Chocolate Cookie | 90 | 450 | 5 | 1.00 | |
Zaxby’s: Brownie | 90 | 360 | 4 | 0.99 | |
Hardee’s: Natural-Cut French Fries (small) | 90 | 360 | 4 | ||
Arby’s: Apple Crisp | 95 | 570 | 6 | 2.49 | |
homemade gingerbread cookie | 100 | 100 | 1 | 0.00 | Dr. Oden brought cookies to share today |
Banana (medium) | 105 | 105 | 1 | 0.22 | |
Arby’s: Salted Caramel & Chocolate Cookie | 107 | 430 | 4 | 1.00 | |
Chick-Fil-A: Hash Rounds (1 box) | 120 | 240 | 2 | 1.19 | |
Hardee’s: Hash Rounds (small) | 130 | 260 | 2 | 0.00 | Price included in combo |
Honeycrisp apple (208g) | 216 | 108 | 0.5 | 0.00 | Check price |
Zaxby’s: Honey Mustard (1 portion cup) | 330 | 330 | 1 | 0.00 |
[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.
[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.
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
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)
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)
# 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]
# 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)
# 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)
)
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))
# 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")
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"
<<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>>
<<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>>
[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)
# 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())
# 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()
# * 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>>
# * 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>>
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'
# 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.
# * 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
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
(org-fitness-list-food-by "protein" :reverse t)
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))))
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))