{"id":"6a17c8fc9f62bead","slug":"workout-consistency-heatmap","trashed":false,"description":"","likes":18,"publish_level":"live","forks":1,"fork_of":null,"has_importers":true,"update_time":"2026-03-20T19:31:49.928Z","first_public_version":554,"paused_version":null,"publish_time":"2019-12-18T12:25:22.804Z","publish_version":610,"latest_version":610,"thumbnail":"b94b955b597d3fa64fa65a3e920a561d44f29e5bdad7233b9c3f14cadf2af486","default_thumbnail":"b94b955b597d3fa64fa65a3e920a561d44f29e5bdad7233b9c3f14cadf2af486","roles":[],"sharing":null,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"},"creator":{"id":"3954cddf6608132f","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","tier":"pro"},"authors":[{"id":"3954cddf6608132f","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","name":"Volodymyr Agafonkin","login":"mourner","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","tier":"pro","approved":true,"description":""}],"collections":[{"id":"7f9c38896ce07772","type":"public","slug":"personal","title":"Personal","description":"","update_time":"2020-04-30T13:39:18.080Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"0adf51ca7fec6a517f10fd007cf6aac62325e114a8e7da2594f09d66da29a7c0","thumbnail":"0adf51ca7fec6a517f10fd007cf6aac62325e114a8e7da2594f09d66da29a7c0","listing_count":8,"parent_collection_count":0,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"}},{"id":"ec2df8b99596790b","type":"public","slug":"data-visualizations","title":"Data Visualizations","description":"","update_time":"2020-01-15T20:55:13.704Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"730057664927661ec4758d2802649b18f1360bdffc7eb0b03716c76339d7ecee","thumbnail":"730057664927661ec4758d2802649b18f1360bdffc7eb0b03716c76339d7ecee","listing_count":5,"parent_collection_count":0,"owner":{"id":"78f29d059906819a","avatar_url":"https://avatars.observableusercontent.com/avatar/02cfd655debb6fc593dfe4bcacd9520f73e6b33cedef382248d08ecc40822a1b","login":"mourner","name":"Volodymyr Agafonkin","bio":"Engineer at Mapbox, creator of Leaflet, algorithms geek, open source enthusiast, musician, father of twin girls, Ukrainian. 🇺🇦","home_url":"https://agafonkin.com","type":"team","tier":"starter_2024"}}],"files":[{"id":"109fe818e8c97bbbf372933a442bf0b41e05d2247696257701b1da7f1611934064961766e3066716ced42a4419eca9e8a503b6fe5562637cebbff0d4b76bba8f","url":"https://static.observableusercontent.com/files/109fe818e8c97bbbf372933a442bf0b41e05d2247696257701b1da7f1611934064961766e3066716ced42a4419eca9e8a503b6fe5562637cebbff0d4b76bba8f","download_url":"https://static.observableusercontent.com/files/109fe818e8c97bbbf372933a442bf0b41e05d2247696257701b1da7f1611934064961766e3066716ced42a4419eca9e8a503b6fe5562637cebbff0d4b76bba8f?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%2711405-vladimiragafonkin%25202021-05-04%2520192709%25401.csv","name":"11405-vladimiragafonkin 2021-05-04 192709@1.csv","create_time":"2021-05-04T19:27:41.434Z","mime_type":"text/csv","status":"public","size":129431,"content_encoding":"gzip","private_bucket_id":null},{"id":"7c52ea998a6d0f4360f2d9e1b24020723acd21e50b56263e17ec07f138bf2a02c9c2eb58201624e1e182d94bac41cdc218b48087a7d693619134387e57c14445","url":"https://static.observableusercontent.com/files/7c52ea998a6d0f4360f2d9e1b24020723acd21e50b56263e17ec07f138bf2a02c9c2eb58201624e1e182d94bac41cdc218b48087a7d693619134387e57c14445","download_url":"https://static.observableusercontent.com/files/7c52ea998a6d0f4360f2d9e1b24020723acd21e50b56263e17ec07f138bf2a02c9c2eb58201624e1e182d94bac41cdc218b48087a7d693619134387e57c14445?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%2711405-vladimiragafonkin%25202022-04-18%2520082716.csv","name":"11405-vladimiragafonkin 2022-04-18 082716.csv","create_time":"2022-04-18T08:27:29.598Z","mime_type":"text/csv","status":"public","size":161757,"content_encoding":"gzip","private_bucket_id":null},{"id":"65152f5c7e9678cfae638a5b2a696bb4303bd1b492200149fe598575a6f91498a06886205c05b3f0490d353b60b17feff87bd8721b8fce997161a692de83e1d0","url":"https://static.observableusercontent.com/files/65152f5c7e9678cfae638a5b2a696bb4303bd1b492200149fe598575a6f91498a06886205c05b3f0490d353b60b17feff87bd8721b8fce997161a692de83e1d0","download_url":"https://static.observableusercontent.com/files/65152f5c7e9678cfae638a5b2a696bb4303bd1b492200149fe598575a6f91498a06886205c05b3f0490d353b60b17feff87bd8721b8fce997161a692de83e1d0?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%2711405-vladimiragafonkin%25202022-07-09%2520104451.csv","name":"11405-vladimiragafonkin 2022-07-09 104451.csv","create_time":"2022-07-09T10:45:05.044Z","mime_type":"text/csv","status":"public","size":167884,"content_encoding":"gzip","private_bucket_id":null},{"id":"976504c2c9f3752d9fb62ea429d7f1c3175b79bcadce4699471a15c610657941b5d52c4a5f2eeb35495a224d7d3ce1531a0e69c2f86798f7623e5a4e29553a47","url":"https://static.observableusercontent.com/files/976504c2c9f3752d9fb62ea429d7f1c3175b79bcadce4699471a15c610657941b5d52c4a5f2eeb35495a224d7d3ce1531a0e69c2f86798f7623e5a4e29553a47","download_url":"https://static.observableusercontent.com/files/976504c2c9f3752d9fb62ea429d7f1c3175b79bcadce4699471a15c610657941b5d52c4a5f2eeb35495a224d7d3ce1531a0e69c2f86798f7623e5a4e29553a47?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202022-10-19%2520191057.csv","name":"agafonkin 2022-10-19 191057.csv","create_time":"2022-10-19T19:11:12.837Z","mime_type":"text/csv","status":"public","size":185107,"content_encoding":"gzip","private_bucket_id":null},{"id":"b93f4a300fa943f4f173eb55b63625074635d6019490236e455f00b97c5465d3fa716dc662b0455cc1e37a4e8d775cc2296b3d7f24fcad6ee4cfecd983799a9c","url":"https://static.observableusercontent.com/files/b93f4a300fa943f4f173eb55b63625074635d6019490236e455f00b97c5465d3fa716dc662b0455cc1e37a4e8d775cc2296b3d7f24fcad6ee4cfecd983799a9c","download_url":"https://static.observableusercontent.com/files/b93f4a300fa943f4f173eb55b63625074635d6019490236e455f00b97c5465d3fa716dc662b0455cc1e37a4e8d775cc2296b3d7f24fcad6ee4cfecd983799a9c?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202022-11-20%2520224354.csv","name":"agafonkin 2022-11-20 224354.csv","create_time":"2022-11-20T22:44:04.292Z","mime_type":"text/csv","status":"public","size":193090,"content_encoding":"gzip","private_bucket_id":null},{"id":"6bbd5d1a6d44c8876df596f23b236b1ebf79ae697b3c1093342fc9a6cd06f511d444fc788b6f09ef704ff2f8814992fc6c91e2932c305020024d17c7ef7b593e","url":"https://static.observableusercontent.com/files/6bbd5d1a6d44c8876df596f23b236b1ebf79ae697b3c1093342fc9a6cd06f511d444fc788b6f09ef704ff2f8814992fc6c91e2932c305020024d17c7ef7b593e","download_url":"https://static.observableusercontent.com/files/6bbd5d1a6d44c8876df596f23b236b1ebf79ae697b3c1093342fc9a6cd06f511d444fc788b6f09ef704ff2f8814992fc6c91e2932c305020024d17c7ef7b593e?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202022-12-20%2520122921.csv","name":"agafonkin 2022-12-20 122921.csv","create_time":"2022-12-20T12:29:40.086Z","mime_type":"text/csv","status":"public","size":197319,"content_encoding":"gzip","private_bucket_id":null},{"id":"b45c638036d7b9f3689baeb934ae4baca13a58843bcb595fd1a976dc3ea866b2d2889b5136b146a48bfe4442a4da67163b1d2f1ad46bf4638b72ac1fc8a8415f","url":"https://static.observableusercontent.com/files/b45c638036d7b9f3689baeb934ae4baca13a58843bcb595fd1a976dc3ea866b2d2889b5136b146a48bfe4442a4da67163b1d2f1ad46bf4638b72ac1fc8a8415f","download_url":"https://static.observableusercontent.com/files/b45c638036d7b9f3689baeb934ae4baca13a58843bcb595fd1a976dc3ea866b2d2889b5136b146a48bfe4442a4da67163b1d2f1ad46bf4638b72ac1fc8a8415f?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202023-01-06%2520161115.csv","name":"agafonkin 2023-01-06 161115.csv","create_time":"2023-01-06T16:11:30.431Z","mime_type":"text/csv","status":"public","size":199087,"content_encoding":"gzip","private_bucket_id":null},{"id":"3268dd7a28ae73379cdbe4092e52233792c517b09425022247376bb9947e78bd1a6d183e3399f80bcfd95dc3f27a122f9c1fce743ffa0637575c0b5c5bc850bc","url":"https://static.observableusercontent.com/files/3268dd7a28ae73379cdbe4092e52233792c517b09425022247376bb9947e78bd1a6d183e3399f80bcfd95dc3f27a122f9c1fce743ffa0637575c0b5c5bc850bc","download_url":"https://static.observableusercontent.com/files/3268dd7a28ae73379cdbe4092e52233792c517b09425022247376bb9947e78bd1a6d183e3399f80bcfd95dc3f27a122f9c1fce743ffa0637575c0b5c5bc850bc?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202024-02-17%2520223702.csv","name":"agafonkin 2024-02-17 223702.csv","create_time":"2024-02-17T22:37:21.127Z","mime_type":"text/csv","status":"public","size":236983,"content_encoding":"gzip","private_bucket_id":null},{"id":"c466640c9d6b3feb6a754b51ddb4f0e287f36cd3929d055775f6747eaa2f2bddac8914bdbb6bfb11e2878f8d72cd118941ab0342ff0c1756843c91e24c5facc8","url":"https://static.observableusercontent.com/files/c466640c9d6b3feb6a754b51ddb4f0e287f36cd3929d055775f6747eaa2f2bddac8914bdbb6bfb11e2878f8d72cd118941ab0342ff0c1756843c91e24c5facc8","download_url":"https://static.observableusercontent.com/files/c466640c9d6b3feb6a754b51ddb4f0e287f36cd3929d055775f6747eaa2f2bddac8914bdbb6bfb11e2878f8d72cd118941ab0342ff0c1756843c91e24c5facc8?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202024-05-16%2520073149.csv","name":"agafonkin 2024-05-16 073149.csv","create_time":"2024-05-16T09:09:57.544Z","mime_type":"text/csv","status":"public","size":244102,"content_encoding":"gzip","private_bucket_id":null},{"id":"32930e41787cac4da179fb4a56e9d29d4bed23ee7cd42416d2de024e2d3e0c5020c765eb4a809549caef9c3b27202aad97ce4d0e876cd1d95193e05651b9922c","url":"https://static.observableusercontent.com/files/32930e41787cac4da179fb4a56e9d29d4bed23ee7cd42416d2de024e2d3e0c5020c765eb4a809549caef9c3b27202aad97ce4d0e876cd1d95193e05651b9922c","download_url":"https://static.observableusercontent.com/files/32930e41787cac4da179fb4a56e9d29d4bed23ee7cd42416d2de024e2d3e0c5020c765eb4a809549caef9c3b27202aad97ce4d0e876cd1d95193e05651b9922c?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202025-12-20%2520164042.csv","name":"agafonkin 2025-12-20 164042.csv","create_time":"2025-12-20T16:46:03.075Z","mime_type":"text/csv","status":"public","size":277109,"content_encoding":"gzip","private_bucket_id":null},{"id":"24ebf3037f24425eeee7805695543355d58b51ad5f5fb1b210c31fbc131f8d014a75817926de366aedc4eccdc81cbedaafcdc8918db7765e9e0a5b093fc4c5ff","url":"https://static.observableusercontent.com/files/24ebf3037f24425eeee7805695543355d58b51ad5f5fb1b210c31fbc131f8d014a75817926de366aedc4eccdc81cbedaafcdc8918db7765e9e0a5b093fc4c5ff","download_url":"https://static.observableusercontent.com/files/24ebf3037f24425eeee7805695543355d58b51ad5f5fb1b210c31fbc131f8d014a75817926de366aedc4eccdc81cbedaafcdc8918db7765e9e0a5b093fc4c5ff?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202026-01-12%2520220036.csv","name":"agafonkin 2026-01-12 220036.csv","create_time":"2026-01-12T22:00:47.419Z","mime_type":"text/csv","status":"public","size":281121,"content_encoding":"gzip","private_bucket_id":null},{"id":"44cfddd7ae620c71400deb33bd468a39193df2fab1e0710cb6ca7a792308360d7cf4a5f6641b67695c0197566b4b479fc15bf402aaf35e6caf8599beb4c3d396","url":"https://static.observableusercontent.com/files/44cfddd7ae620c71400deb33bd468a39193df2fab1e0710cb6ca7a792308360d7cf4a5f6641b67695c0197566b4b479fc15bf402aaf35e6caf8599beb4c3d396","download_url":"https://static.observableusercontent.com/files/44cfddd7ae620c71400deb33bd468a39193df2fab1e0710cb6ca7a792308360d7cf4a5f6641b67695c0197566b4b479fc15bf402aaf35e6caf8599beb4c3d396?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27agafonkin%25202026-03-20%2520192402.csv","name":"agafonkin 2026-03-20 192402.csv","create_time":"2026-03-20T19:26:01.479Z","mime_type":"text/csv","status":"public","size":293076,"content_encoding":"gzip","private_bucket_id":null}],"comments":[],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":610,"title":"Workout Consistency Heatmap","license":null,"copyright":"","nodes":[{"id":0,"value":"md`# Workout Consistency Heatmap\n\nStrength training is one of the best activities for improving one's overall health and well-being. Its main principle is simple: if you train regularly, you slowly build strength & muscle; otherwise you slowly loose them. Consistency is considered way more important than the choice of program.\n\nAs a geek with short attention span who's terrible at establishing habits, I always spent way more time on reading & research about strength training than actually training in the gym, so despite the best choice of programming & exercise technique, I failed miserably on the most important part — consistent training. At times I could blame injuries, haphazard travel, or overload on work & family commitments, but for the most part, I'm just terrible at organizing myself and following a routine. I want to change that. To visualize how terrible I'd been and how well I'll be doing going forward, I made the chart below.\n\n_Edit_: a few months after publishing this, the pandemic started. Welp, there goes my workout routine... Still, I'm hoping for the best and trying to get at least some workouts in to maintain strength until everything goes back to normal.\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":109,"value":"{\n  const startDate = Date.parse(dates[0]);\n  const startYear = new Date(startDate).getYear();\n  const numYears = new Date(dates[dates.length - 1]).getYear() - startYear + 1;\n\n  const maxWeeks = 53;\n  const size = Math.floor(width / maxWeeks);\n  const margin = Math.max(1, Math.round(size / 4));\n  const ctx = DOM.context2d(size * maxWeeks, (numYears * 7) * size + (numYears - 1) * margin);\n  \n  ctx.lineWidth = 2;\n  \n  for (let i = 0; i < days.length; i++) {\n    const d = days[i];\n    const date = new Date(startDate + i * dayMs);\n    const yearStart = new Date(date);\n    yearStart.setMonth(0);\n    yearStart.setDate(1);\n    \n    ctx.fillStyle = d < 0 ? '#aaa' : colors[d];\n    const x = Math.floor((yearStart.getDay() + Math.round((date - yearStart) / dayMs)) / 7) * size;\n    const y = (date.getYear() - startYear) * (7 * size + margin) + date.getDay() * size;\n    ctx.fillRect(x, y, size, size);\n\n    if (d === 0) {\n      const r = Math.max(1, size / 7);\n      const c = size / 2;\n      ctx.fillStyle = 'rgba(255,255,255,0.5)';\n      ctx.beginPath();\n      ctx.arc(x + c, y + c, r, 0, 2 * Math.PI, false);\n      ctx.fill();\n    }\n    const day = date.getDate();\n    if (day <= 7 && date.getMonth() > 0) {\n      ctx.beginPath();\n      ctx.moveTo(x, y + size);\n      ctx.lineTo(x, y);\n      if (day === 1 && date.getDay() > 0) {\n        ctx.lineTo(x + size, y);\n      }\n      ctx.stroke();\n    }\n  }\n  return ctx.canvas;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":455,"value":"md`\nThe chart visualizes the last few years of my training (2017—2025), where: \n- Light dots are the days I did a workout (full-body: push, pull, legs).\n- <strong style=\"color: ${colors[0]}\">Green</strong> cells are the days I gained strength & muscle.\n- <strong style=\"color: ${colors[colors.length - 1]}\">Red</strong> cells are the days I lost them.\n\nFor the general population (not advanced athletes), experts agree on a 2–3 days interval (~3 times per week) as an optimal timing for training a muscle group; 1 per week is considered maintenance level, with the negative trend taking over as the interval increases. Hence the colors.\n\nData is taken from [StrengthLevel.com](https://strengthlevel.com/11405-vladimiragafonkin), where I track my workouts — it helpfully has a CSV export and is a nice tool overall.\n\nIf you want to start or learn more about strength training, I highly recommend [The Fitness Wiki](https://thefitness.wiki/).\n\n## Appendix\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":541,"value":"data = FileAttachment(\"agafonkin 2026-03-20 192402.csv\")","pinned":false,"mode":"js","data":null,"name":null},{"id":564,"value":"updated = data.name.split(' ')[1]","pinned":false,"mode":"js","data":null,"name":null},{"id":4,"value":"dates = {\n  const lines = (await data.text()).split('\\n').slice(1);\n  const set = new Set();\n  for (const line of lines) set.add(line.split(',')[0]);\n  return [...set.keys()].sort();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":106,"value":"days = {\n  const days = [];\n  const dateUpdated = Date.parse(updated);\n\n  for (let i = 0; i < dates.length; i++) {\n    const current = Date.parse(dates[i]);\n    const next = i === dates.length - 1 ? dateUpdated + dayMs : Date.parse(dates[i + 1]);\n    for (let k = 0, len = Math.round((next - current) / dayMs); k < len; k++) days.push(k);\n  }\n  for (let k = 0, len = Math.floor((Date.now() - dateUpdated) / dayMs); k < len; k++) days.push(-1);\n  return days;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":426,"value":"colors = ['#1a9850', '#1a9850', '#91cf60', '#d9ef8b', '#ffffbf', '#fee08b', '#fc8d59', '#d73027']","pinned":false,"mode":"js","data":null,"name":null},{"id":279,"value":"dayMs = 1000 * 24 * 60 * 60","pinned":false,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}