token = localStorage.getItem('ath_token')
d3 = require('d3')
results = {
yield undefined
yield await fetch("https://api.andrewheiss.com/weight?start_date=2024-01-01&loess=true&forecast=true&forecast_start=2024-08-01", {
body: "",
headers: {
"Authorization": `Bearer ${token}`,
"content-type": "application/json"
},
method: "POST",
mode: "cors"
}).then(response => {
if (!response.ok) {
if(response.status === 401) {
throw new Error('Unauthorized');
} else {
throw new Error('Network error');
}
}
return response.json();
})
.then(data => ({
...data,
wt: data.wt.map(d => ({
...d,
timestamp: new Date(d.timestamp),
timestamp_local_as_utc: new Date(d.timestamp_local_as_utc),
})),
trend: data.trend.map(d => ({
...d,
timestamp_local_as_utc: new Date(d.timestamp_local_as_utc),
})),
forecast: data.forecast.map(d => ({
...d,
timestamp_local_as_utc: new Date(d.timestamp_local_as_utc),
}))
}))
.catch(error => {
if (error.message === 'Network error') {
console.error('Error with the API call:', error);
return error.message;
} else if (error.message === 'Unauthorized') {
console.error('Unauthorized:', error);
return error.message;
} else {
console.error('Some general error:', error);
return "Error";
}
});
}{
const zoom = d3.zoom().on("zoom", handleZoom);
function handleZoom(e) {
const scale = plot.scale("x");
const x = d3.scaleLinear().domain(scale.domain).range(scale.range);
const startDate = new Date(
Math.max(e.transform.rescaleX(x).domain()[0], extent[0].getTime())
);
const endDate = new Date(
Math.min(e.transform.rescaleX(x).domain()[1], extent[1])
);
mutable domain = [startDate, endDate];
}
function initZoom() {
d3.select(plot).call(zoom);
}
initZoom();
}extent = {
if (results == undefined) {
return undefined;
} else if (show_forecast) {
let min = d3.min(results.wt, ({ timestamp_local_as_utc }) => timestamp_local_as_utc);
let max = d3.max(results.forecast, ({ timestamp_local_as_utc }) => timestamp_local_as_utc);
return [min, max];
} else {
return d3.extent(results.wt, ({ timestamp_local_as_utc }) => timestamp_local_as_utc)
}
}
mutable domain = extent;{
let plotContainer = document.querySelector('.weight-plot');
let container = document.getElementById('quarto-content');
let loadingContainer = document.getElementById('loading-alert');
// If the loading container doesn't exist, create it
if (!loadingContainer) {
container.insertAdjacentHTML('afterbegin', `
<div id="loading-alert" class="container mt-3">
</div>
`);
loadingContainer = document.getElementById('loading-alert');
}
if (results === undefined) {
loadingContainer.innerHTML = `
<div class="alert alert-info" role="alert">
<i class="fa-solid fa-clock fa-spin"></i> Loading data…
</div>
`;
plotContainer.style.display = "";
} else if (!token) {
loadingContainer.innerHTML = `
<div class="alert alert-warning" role="alert">
<i class="fa-solid fa-lock"></i> Not logged in!
</div>
`;
plotContainer.style.display = "none";
} else {
loadingContainer.remove();
plotContainer.style.display = "";
}
}{
if (!token) {
// Hide the .observablehq--error div
let errorDiv = document.querySelector('.weight-plot .observablehq--error');
if (errorDiv) {
errorDiv.style.display = 'none';
}
}
}plot = Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Manrope",
},
marginBottom: 40,
marginLeft: 40,
x: {
domain,
label: "Time",
grid: true
},
y: {
label: "Weight",
grid: true
},
color: {
label: "Time of day",
domain: ["Morning", "Evening"],
range: ["#FB9E07", "#771C6D"]
},
marks: [
Plot.axisX({
label: null,
tickSize: 0,
nice: true
}),
Plot.axisY({
tickSize: 0,
nice: true
}),
Plot.ruleY(show_thresholds ? results.bmi : [], {
y: "weight",
stroke: "#42159D",
strokeDasharray: "3,2",
channels: {"Threshold": "threshold"},
tip: {
format: {
Threshold: true,
y: (d) => d.toFixed(2)
}
}
}
),
Plot.areaY(results.trend, {
x: "timestamp_local_as_utc",
y1: "conf_low",
y2: "conf_high",
fillOpacity: 0.1
}),
Plot.line(results.trend, {
x: "timestamp_local_as_utc",
y: "weight_pred",
channels: {"Average weight": "weight_pred"},
tip: !show_trend_tips ? false : {
format: {
x: (d) => d3.utcFormat("%A, %B %e at %H:%M")(d).replace(/ +/g, ' '),
y: false,
"Average weight": (d) => d.toFixed(2)
}
}
}),
Plot.areaY(show_forecast ? results.forecast : [], {
x: "timestamp_local_as_utc",
y1: "conf_low",
y2: "conf_high",
fill: "#A52C60",
fillOpacity: 0.1
}),
Plot.line(show_forecast ? results.forecast : [], {
x: "timestamp_local_as_utc",
y: "weight_pred",
stroke: "#A52C60",
channels: {"Predicted weight": "weight_pred"},
tip: {
format: {
x: (d) => d3.utcFormat("%A, %B %e")(d).replace(/ +/g, ' '),
y: false,
"Predicted weight": (d) => d.toFixed(2)
}
}
}),
Plot.dot(results.wt, {
x: "timestamp_local_as_utc",
y: "weight",
fill: "time_of_day",
r: 5,
tip: show_trend_tips ? false : {
format: {
fill: true,
x: (d) => d3.utcFormat("%A, %B %e at %H:%M")(d).replace(/ +/g, ' '),
y: true
}
}
})
]
})