I've been using Emacs with org-mode
to track my diet since 2012. I've had some
breaks along the way, the overall setup has stayed the same.
I use this system to track how much I weigh, as well as how many calories I'm consuming in a single day. There are plenty of apps and online services that provide this functionality, but I prefer to own my data in an open format I can use elsewhere.
Seeing as I work from home and always have an Emacs session open, it made sense for me to try to utilize Emacs in some way.
My set up uses the following Emacs and org-mode
functionality:
- Individual
org-mode
headlines for each day org-mode
properties for storing my weightorg-mode
tables and spreadsheet formulasorg-capture
for weighing in- Some elisp functions for adding individual food entries
Let's take a closer look at how all of this fits together.
Diet file setup
My diet file looks like this (with headings collapsed):
#+TITLE: Diet Tracker #+SEQ_TODO: CAL-IN | CAL-OUT CAL-CANCEL * Daily Logs ** CAL-OUT Diet for day <2020-07-03 Fri>... ** CAL-OUT Diet for day <2020-07-02 Thu>... ** CAL-OUT Diet for day <2020-07-01 Wed>... ** CAL-OUT Diet for day <2020-06-30 Tue>... ** CAL-OUT Diet for day <2020-06-29 Mon>... ... And more
I tried to keep the file format as simple as possible. Each day has its own
entry underneath the main "Daily Logs" header. The main "Daily Logs" header is
used by org-capture
to find where to place new items.
The top-level SEQ_TODO
property is used to set the "done" state for each
headline. I use three different types of headline:
CAL-IN
- For days where I haven't entered all of my diet information. There's usually only one of these open at a time.
CAL-OUT
- For finished days where all data is final.
CAL-CANCEL
- For days where I'm tracking weight but not calorie information. I use this for Sundays, days where I go out to eat, or when I have social events planned.
Daily entries
A daily entry looks something like this:
** CAL-OUT Diet for day <2019-10-05 Sat> :PROPERTIES: :Weight: 167.3 :END: | Timestamp | Food | Calories | Quantity | Total | |------------------------+------------------------+----------+----------+---------| | [2019-10-05 Sat 08:42] | Cup of tea | 54 | 1 | 54 | | [2019-10-05 Sat 11:34] | Chocolate Chip Pancake | 2.7 | 202 | 545.4 | | [2019-10-05 Sat 14:46] | Cup of tea | 54 | 1 | 54 | | [2019-10-05 Sat 14:46] | Vanilla yogurt raisins | 4.33 | 30 | 129.9 | | [2019-10-05 Sat 17:45] | Beef meatballs | 2.02 | 370 | 747.4 | | [2019-10-05 Sat 17:45] | Fresh Pasta | 1.31 | 306 | 400.86 | | [2019-10-05 Sat 17:45] | Garlic bread | 2.75 | 94 | 258.5 | | [2019-10-05 Sat 19:58] | Cup of tea | 54 | 1 | 54 | |------------------------+------------------------+----------+----------+---------| | Total | | | | 2244.06 | #+TBLFM: $5=$3*$4::@>$5=vsum(@2$5..@-I$5)
It's a fairly simple table that uses some org-mode
magic for calculating
totals. The "Calories" column is usually "calories per gram", but for some items
it's "calories per item". Likewise, the quantity column either refers to the
weight in grams or the number of items consumed.
The #+TBFLM:
part underneath the table is an org-mode
spreadsheet
formula. It uses two formulae:
$5=$3*$4
- Sets column 5 (the "Totals" column) to Calories x
Quantity.
org-mode
columns indexes start from 1 rather than 0. @>$5=vsum(@2$5..@-I$5)
-
Calculates the total amount of calories consumed during the day. It uses relative references so that it works no matter how many lines
I previously used
$LR5
instead of@>$5
to reference the footer row, but this no longer worked after upgrading to org-mode 9.4.
The Spreadsheet section of the org-mode
manual goes into detail on
formulas. It took me a while to get the hang of, but it's a really powerful
system.
Weigh-ins
I use an org-capture-template
for my weigh-ins. I weigh myself every morning,
depending on my schedule.
org-capture
is bound to C-c o r
, and then my weigh-in template is bound to
w
. So every day I run C-c o r w
, enter my weight, then use C-c C-c
to save
it to my diet file. And that's it.
My capture template is below:
(add-to-list org-capture-templates '("w" "Weigh-in" entry (file+headline "~/self/diet.org" "Daily Logs") "* CAL-IN Diet for day %t %^{Weight}p | Timestamp | Food | Calories | Quantity | Total | |--------------+---+----------+----------+-------| |--------------+---+----------+----------+-------| | Total | | | | | #+TBLFM: $5=$3*$4::@>$5=vsum(@2$5..@-I$5)" :prepend t))
Adding new food entries
I have a couple of elisp functions I use for adding new data. The primary
function is org-diet-copy
, which is bound to C-c C-C
.
org-diet-copy
is used on a row of another table. It copies the food name,
calorie amount, and quantity to the top table and replaces the timestamp with
the current date and time.
The process of adding a new entry typically goes like this:
- Hit
C-s
to search for the food I want to add. If I wanted to add a new entry for "french fries" I would probably do something like "C-s fren" to find the first "french fries" entry. - Hit
C-c C-C
to copy the entry to my active day. - Replace the quantity with whatever amount I ate. I try to keep the same portion size for breakfast and snacks, so this isn't always necessary.
- Run
M-x org-table-recalculate
to update the table.
It's a simple system, but it works well enough. Eventually I may add function that prompts for a food and quantity, and then automatically fills in the calorie amount.
All of the functions I use are below:
(defun org-diet-move-today () "Move to today's entry in the org-diet file." (interactive) (beginning-of-buffer) ;; Move to first heading (Daily Logs) and expand. (outline-next-visible-heading 1) (show-children) (outline-next-visible-heading 1)) (defun org-diet-move-last-entry () "Move to the last entry in the current diet table." (interactive) (search-forward "#+TBLFM") ;; Move to start of final line. (previous-line 1) (previous-line 1) (move-beginning-of-line 1)) (defun org-diet-copy () "Copy the current table line to today. Copies the current table line and moves it to the bottom of today's diet table. Changes the timestamp to the current time and day." (interactive) (let ((diet-line (thing-at-point 'line t))) ;; Jump to today & last line. (org-diet-move-today) (org-diet-move-last-entry) ;; Insert the copied line. (insert diet-line) ;; Move back to last line. (org-diet-move-last-entry) (forward-line -1) ;; Replace the date. (beginning-of-line) (search-forward "| ") (zap-to-char 1 93) (insert (format-time-string "[%Y-%m-%d %a %H:%M]" (current-time))) ;; Update the table. (forward-line 2) (org-table-recalculate) ;; Move to inserted line. (beginning-of-line) (forward-line -2))) ;; Bind copying to `C-c C-C`. (global-set-key "\C-cC" #'org-diet-copy)
In part two I'll cover the system I use to extract data from the org-mode
file. It's not pretty.
1 Comment
Just out of curiosity, for your non-packaged items, where do you get calorie data?