all_people = ["alexander", "andrew", "benjamin", "miriam", "nancy", "rachel", "zoe"]
schema_loading = all_people
.reduce((obj, person) => {
obj[person] = {
name: [person],
count: [html`<i class="fa fa-clock fa-spin"></i>`],
most_recent: {
title: [html` <i class="fa-solid fa-ellipsis fa-fade"></i>`],
author: [html` <i class="fa-solid fa-ellipsis fa-fade"></i>`],
date: [html` <i class="fa-solid fa-ellipsis fa-fade"></i>`]
},
data: {
timestamp_local: null,
total: null
}
};
return obj;
}, {});
schema_404 = all_people
.reduce((obj, person) => {
obj[person] = {
name: [person],
count: [html`<span class="fa-stack"><i class="fa-solid fa-cloud fa-stack-1x"></i><i class="fa-solid fa-slash fa-stack-1x" style="color:#FF4136"></i></span>`],
most_recent: {
title: [html` <i class="fa-solid fa-triangle-exclamation"></i>`],
author: [html` <i class="fa-solid fa-triangle-exclamation"></i>`],
date: [html` <i class="fa-solid fa-triangle-exclamation"></i>`]
},
data: {
timestamp_local: null,
total: null
}
};
return obj;
}, {});
schema_error = all_people
.reduce((obj, person) => {
obj[person] = {
name: [person],
count: [html`<i class="fa-solid fa-bomb"></i>`],
most_recent: {
title: [html` <i class="fa-solid fa-bomb"></i>`],
author: [html` <i class="fa-solid fa-bomb"></i>`],
date: [html` <i class="fa-solid fa-bomb"></i>`]
},
data: {
timestamp_local: null,
total: null
}
};
return obj;
}, {});
d3 = require("d3")
books = {
// Provide an empty array with placeholder values until the data loads
yield schema_loading
// yield undefined
yield fetch("https://api.andrewheiss.com/books?person=all&start_date=2025-01-01&end_date=2025-12-31")
.then(response => {
if (!response.ok) {
throw new Error('Network error');
}
return response.json();
})
.then(people => {
for (let person in people) {
people[person].data = people[person].data.map(d => ({
...d,
timestamp_local: new Date(d.timestamp_local_as_utc)
}));
}
return people;
})
.catch(error => {
if (error.message === 'Network error') {
console.error('Error with the API call:', error);
return schema_404;
} else {
console.error('Some general error:', error);
return schema_error;
}
});
}
Alexander’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.alexander.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#1D6996",
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})
Zoë’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.zoe.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#5F4690",
size: 5,
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})
Benjamin’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.benjamin.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#0F8554",
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})
Miriam’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.miriam.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#CC503E",
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})
Rachel’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.rachel.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#38A6A5",
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})
Nancy’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.nancy.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#94346E",
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})
Andrew’s total books
Plot.plot({
style: {
fontSize: "14px",
fontFamily: "Libre Franklin",
},
marginBottom: 0,
marginLeft: 0,
y: {
label: "Books read",
grid: false,
},
x: {
label: "Month",
domain: [new Date("2025-01-01"), new Date("2025-12-31")]
},
marks: [
Plot.axisX({label: null, ticks: null}),
Plot.axisY({label: null, ticks: null}),
Plot.rectY(books.andrew.data,
Plot.binX(
{y: "count"}, // Reducing function
{
x: "timestamp_local",
y: "total",
fill: "#E17C05",
interval: d3.utcMonth,
inset: 2,
tip: {
format: {
x1: (d) => d3.utcFormat("%B")(d),
x2: false,
y: true
}
}
}
)
)
]
})