// Empty JSON of expected results
schema_results = {
return {
"fitbit_summary": [
{
"id": "1",
"date": "2024-01-01",
"createdTime": "2024-01-15T19:50:36.000Z",
"steps": 0,
"floors": 0,
"restingHeartRate": 0,
"marginalCalories": 0,
"exercise_minutes": 0,
"distance": 0,
"date_actual": "2024-01-01",
"month_num": 1,
"month_chr": "January",
"weekday_num": 2,
"weekday_chr": "Monday"
}
],
"activities": [
{
"date": "2024-01-01",
"name": null,
"duration": 0
}
]
}
}import { aq, op } from "@uwdata/arquero"
d3 = require("d3")
token = localStorage.getItem('ath_token')
results_raw = {
// Provide an empty array with placeholder values until the data loads
yield schema_results
yield await fetch("https://api.andrewheiss.com/activity?start_date=2025-01-01", {
body: "",
headers: {
"Authorization": `Bearer ${token}`,
"content-type": "application/json"
},
method: "POST",
mode: "cors"
}).then(response => {
if (!response.ok) {
console.error(response);
throw new Error('Network error');
}
return response.json();
})
.catch(error => {
if (error.message === 'Network error') {
console.error('Error with the API call:', error);
return schema_results;
} else {
console.error('Some general error:', error);
return schema_results;
}
});
}daily = aq.from(results_raw.fitbit_summary)
.derive({date: d => op.parse_date(d.date_actual)})
// lol javascript is 0 indexed
.derive({
weekday_num: d => d.weekday_num - 1,
month_num: d => d.month_num - 1
})
totals = daily
.rollup({
total_minutes: d => op.sum(d.exercise_minutes),
total_distance: d => op.sum(d.distance),
total_steps: d => op.sum(d.steps)
})
text_total_minutes = op.round(totals.get('total_minutes', 0)).toLocaleString()
text_total_distance = op.round(totals.get('total_distance', 0)).toLocaleString()
text_total_steps = op.round(totals.get('total_steps', 0)).toLocaleString()goal_minutes = 10000
total_minutes = totals.get('total_minutes', 0)
pct_goal = total_minutes / goal_minutes
pct_goal_truncated = pct_goal >= 1 ? 1 : pct_goal
year_info = {
const empty_date = new Date();
const start_of_year = new Date(empty_date.getFullYear(), 0, 1);
const end_of_year = new Date(empty_date.getFullYear() + 1, 0, 1);
const year_progress = ((empty_date - start_of_year) / (end_of_year - start_of_year));
const isLeapYear = (empty_date.getFullYear() % 4 == 0) && (empty_date.getFullYear() % 100 != 0) || (empty_date.getFullYear() % 400 == 0);
const daysInYear = isLeapYear ? 366 : 365;
const diff = empty_date - start_of_year;
const oneDay = 1000 * 60 * 60 * 24;
const day = Math.floor(diff / oneDay) + 1;
const text = "Day " + day + " of " + daysInYear;
return {
pct_year: year_progress,
days_in_year: daysInYear,
yday: day,
text: text
}
}
progress_data = aq.from([
{type: "Year complete", name: "Completed", value: year_info.pct_year,
label_right: `${(year_info.pct_year * 100).toFixed(2)}%`,
label_left: year_info.text},
{type: "Year complete", name: "Remaining", value: 1 - year_info.pct_year},
{type: "Exercise minutes", name: "Completed", value: pct_goal_truncated,
label_right: `${(pct_goal * 100).toFixed(2)}%`,
label_left: op.round(total_minutes).toLocaleString() + " of " + goal_minutes.toLocaleString() + " minutes"},
{type: "Exercise minutes", name: "Remaining", value: 1 - pct_goal_truncated}
])
weekday_order = ["Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"]
month_order = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"]
by_weekday = daily
.groupby("weekday_num")
.rollup({
total_minutes: d => op.sum(d.exercise_minutes),
avg_minutes: d => op.mean(d.exercise_minutes)
})
by_month = daily
.groupby("month_num")
.rollup({
total_minutes: d => op.sum(d.exercise_minutes),
avg_minutes: d => op.mean(d.exercise_minutes)
})
mappings = {
return {
"Total": { label: "Total minutes", variable: "total_minutes" },
"Average": { label: "Average minutes", variable: "avg_minutes" }
}
}Plot.plot({
color: {
range: ["#FB9E07", "#868e96"]
},
x: {axis: null},
y: {axis: null},
marks: [
Plot.barX(progress_data, {
x: "value",
y: "type",
fill: "name"
}),
Plot.text(progress_data.filter(d => d.name == "Completed"), {
x: 0,
y: "type",
text: "label_left",
fill: "white",
frameAnchor: "middle",
textAnchor: "start",
dx: 5,
fontWeight: "bold",
fontSize: 15,
fontFamily: "Manrope"
}),
Plot.text(progress_data.filter(d => d.name == "Completed"), {
x: 1,
y: "type",
text: "label_right",
fill: "white",
frameAnchor: "middle",
textAnchor: "end",
dx: -5,
fontWeight: "bold",
fontSize: 15,
fontFamily: "Manrope"
})
]
})
Total minutes
Steps recorded
Miles recorded
Plot.plot({
x: {
label: null,
domain: Array.from({length: 12}, (_, i) => i)
},
y: {
label: mappings[month_value_to_show].label,
grid: 4
},
marks: [
Plot.ruleY([0]),
Plot.axisX({
ticks: 12,
tickFormat: Plot.formatMonth("en", "short"),
tickSize: 0,
fontFamily: "Manrope"
}),
Plot.axisY({
ticks: 4,
tickSize: 0,
nice: true,
fontFamily: "Manrope"
}),
Plot.barY(by_month, {
x: "month_num",
y: mappings[month_value_to_show].variable,
fill: "#17a2b8",
tip: {
format: {
x: false,
y: (d) => d.toFixed(0)
},
fontFamily: "Manrope"
}
})
]
})Plot.plot({
x: {
label: null,
domain: Array.from({length: 7}, (_, i) => i)
},
y: {
label: mappings[day_value_to_show].label,
grid: 4
},
marks: [
Plot.ruleY([0]),
Plot.axisX({
ticks: 7,
tickFormat: Plot.formatWeekday("en", "short"),
tickSize: 0,
fontFamily: "Manrope"
}),
Plot.axisY({
ticks: 4,
tickSize: 0,
nice: true,
fontFamily: "Manrope"
}),
Plot.barY(by_weekday, {
x: "weekday_num",
y: mappings[day_value_to_show].variable,
fill: "#A52C60",
tip: {
format: {
x: false,
y: (d) => d.toFixed(0)
},
fontFamily: "Manrope"
}
})
]
})