Public
Edited
Jul 26, 2024
Insert cell
Insert cell
Insert cell
data = await streaming_history_2022.map((d) => {
const playedAt = new Date(d.played_at);
const tzdate = Temporal.Instant.from(
playedAt.toISOString()
).toZonedDateTimeISO("America/Los_Angeles"); // date in localized timezone
const date = new Date(tzdate.epochMilliseconds); // Javascript Date object, stores date in browser's local timezone
const time = new Date(
(tzdate.epochMilliseconds - 8 * 60 * 60 * 1000) % (24 * 60 * 60 * 1000)
); // "Truncate" year, month, day
const hour = tzdate.hour;
const minute = tzdate.minute;
const dayOfMonth = tzdate.day;
const day = weekdays[tzdate.dayOfWeek + -1];
const weekend = tzdate.dayOfWeek > 5 ? "Weekend" : "Weekday";
const week = tzdate.weekOfYear;
const month = months[tzdate.month - 1];
const quarter =
tzdate.month < 4
? "Q1: Jan - Mar"
: tzdate.month < 7
? "Q2: Apr - Jun"
: tzdate.month < 10
? "Q3: Jul - Sep"
: "Q4: Oct - Dec";
const timeOfDay =
tzdate.hour < 5
? "Late night"
: tzdate.hour < 10
? "Early morning"
: tzdate.hour < 13
? "Morning"
: tzdate.hour < 17
? "Afternoon"
: tzdate.hour < 20
? "Late afternoon"
: "Evening";
const context = d.context;
const track = d.track;
const artistsNames = d.track.artists.map((v) => v.name);
return {
date,
time,
hour,
minute,
day,
weekend,
week,
month,
quarter,
dayOfMonth,
timeOfDay,
track,
playedAt,
context,
artistsNames
};
})
Insert cell
Insert cell
Insert cell
Plot.plot({
height: 400,
width,
axis: null,
x: {
axis: "top"
},
y: {
axis: null,
domain: [0, 35]
},
fy: {
axis: "right",
label: null,
domain: domains.day,
padding: 0.05
},
facet: {
data: data,
y: "day",
marginRight: 100
},
marks: [
Plot.areaY(
data,
Plot.binX(
{ y: "count" },
{
x: "time",
fillOpacity: 0.2,
thresholds: d3.timeMinute.every(15),
curve: "basis"
}
)
),
Plot.lineY(
data,
Plot.binX(
{ y: "count" },
{
x: "time",
strokeWidth: 1,
thresholds: d3.timeMinute.every(15),
curve: "basis"
}
)
)
]
})
Insert cell
Plot.plot({
width,
y: { domain: domains.day, label: "" },
x: { ticks: 2 },
facet: { data: data, x: "month" },
fx: { domain: domains.month, label: "" },
marginLeft: 100,
padding: 0.1,
marks: [
Plot.frame(),
Plot.tickX(data, {
x: "time",
y: "day",
strokeOpacity: 0.1
})
]
})
Insert cell
Plot.plot({
width,
y: { domain: domains.month, label: "", padding: 0.005 },
x: { ticks: 4 },
facet: { data: data, x: "weekend" },
fx: { label: "" },
marginLeft: 100,
padding: 0.05,
marks: [
Plot.frame(),
Plot.tickX(data, {
x: "time",
y: "month",
strokeOpacity: 0.1
})
]
})
Insert cell
Plot.plot({
width,
marginTop: 20,
marks: [
Plot.frame(),
Plot.tickX(
data,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "time",
thresholds: d3.timeMinute
}
)
)
]
})
Insert cell
Plot.plot({
width,
marginTop: 20,
x: { label: "" },
marks: [
Plot.frame(),
Plot.tickX(
data,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "playedAt",
thresholds: d3.timeMinute
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { ticks: 3, tickFormat: "%-I%p" },
facet: { data: data, x: "day", y: "month", marginLeft: 100 },
fx: { domain: domains.day, label: "" },
fy: { domain: domains.month, label: "" },
padding: 0,
marks: [
Plot.frame({ strokeOpacity: 0.3 }),
Plot.tickX(data, {
x: "time",
strokeOpacity: 0.1
})
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
width,
marginTop: 20,
x: { tickFormat: "%-I %p" },
marks: [
Plot.frame(),
Plot.tickX(
artistData,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "time",
thresholds: d3.timeMinute
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { ticks: 3, line: true, tickFormat: "%-I %p" },
facet: { data: artistData, x: "day", marginTop: 20 },
fx: { domain: domains.day, label: "", line: true },
padding: 0,
marks: [
Plot.frame(),
Plot.tickX(
artistData,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "time",
thresholds: d3.timeMinute
// stroke: "timeOfDay"
}
)
)
]
})
Insert cell
Plot.plot({
width,
marginTop: 20,
marks: [
Plot.frame(),
Plot.tickX(
artistData,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "playedAt",
thresholds: d3.timeMinute
}
)
)
]
})
Insert cell
Plot.plot({
width,
marginTop: 20,
x: { label: "", tickFormat: "%-I %p" },
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "time",
thresholds: d3.timeMinute.every(5),
stroke: "purple",
strokeOpacity: 0.3
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { ticks: 3, tickFormat: "%-I %p" },
facet: { data: artistData, x: "day" },
fx: { domain: domains.day, label: "", marginTop: 20 },
padding: 0,
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "time",
thresholds: d3.timeMinute.every(30),
stroke: "purple",
strokeOpacity: 0.3
// stroke: "timeOfDay"
}
)
)
]
})
Insert cell
Plot.plot({
width,
marginTop: 20,
x: { label: "" },
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
thresholds: d3.timeHour,
stroke: "purple",
strokeOpacity: 0.3
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { label: "" },
y: { tickFormat: "%-I %p" },
marks: [
Plot.dot(
artistData,
Plot.bin(
{ r: "count" },
{
x: "playedAt",
y: "time",
thresholds: d3.timeMinute.every(15),
stroke: "purple",
strokeOpacity: 0.5
}
)
)
]
})
Insert cell
Plot.plot({
width,
facet: { data: artistData, y: "timeOfDay", marginLeft: 100, marginTop: 20 },
fy: { domain: domains.timeOfDay, label: "" },
x: { label: "" },
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
thresholds: d3.timeHour,
strokeOpacity: 0.4,
stroke: "purple"
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { label: "", ticks: 3 },
y: { tickFormat: "%-I %p" },
facet: { data: artistData, x: "month" },
fx: { label: "", domain: domains.month },
padding: 0,
marks: [
Plot.dot(
artistData,
Plot.binY(
{ r: "count" },
{
y: "time",
x: "dayOfMonth",
thresholds: d3.timeMinute.every(30),
stroke: "purple",
strokeOpacity: 0.5
}
)
)
]
})
Insert cell
Plot.plot({
width,
y: { domain: domains.day, label: "" },
x: { ticks: 3, round: true, tickFormat: "%H" },
facet: { data: data, x: "month" },
fx: { domain: domains.month, label: "" },
marginLeft: 70,
padding: 0.05,
marks: [
Plot.frame(),
Plot.dotX(
data,
Plot.binX(
{ r: "count" },
{
x: "time",
y: "day",
strokeOpacity: 0.4,
thresholds: d3.timeMinute.every(20)
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { label: "", inset: 20 },
y: { ticks: false },
marginLeft: 120,
marks: [
Plot.dot(
artistData.filter((d) => d.track.album.album_type == "album"),
Plot.binX(
{ r: "count" },
{
x: "time",
y: (d) => d.track.album.name,
thresholds: d3.timeMinute.every(10),
stroke: "purple",
strokeOpacity: 0.5,
sort: { y: "r", reverse: true, limit: 10 }
}
)
),
Plot.image(artistData, {
x: 1,
y: (d) => d.track.album.name,
src: (d) => d.track.album.imageUrl,
dx: -20
})
]
})
Insert cell
Plot.plot({
width,
x: { label: "", ticks: 3, inset: 20 },
marginLeft: 125,
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "time",
y: (d) => d.track.name,
thresholds: d3.timeMinute.every(10),
stroke: "purple",
strokeOpacity: 0.5,
sort: { y: "r", reverse: true, limit: 10 }
}
)
),
Plot.image(artistData, {
x: 1,
y: (d) => d.track.name,
src: (d) => d.track.album.imageUrl,
dx: -20
})
]
})
Insert cell
Plot.plot({
width,
x: { label: "", ticks: 3, inset: 20 },
marginLeft: 125,
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
y: (d) => d.track.album.name,
thresholds: d3.timeDay,
stroke: "purple",
strokeOpacity: 0.5,
sort: { y: "r", reverse: true, limit: 10 }
}
)
),
Plot.ruleX(monthRules, { strokeOpacity: 0.4 }),
Plot.image(artistData, {
x: d3.min(artistData.map((d) => d.date)),
y: (d) => d.track.album.name,
src: (d) => d.track.album.imageUrl,
dx: -20
})
]
})
Insert cell
Plot.plot({
width,
x: { label: "", ticks: 3, inset: 20 },
marginLeft: 125,
marks: [
Plot.dot(
artistData,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
y: (d) => d.track.name,
z: (d) => d.track.album.name,
thresholds: d3.timeDay,
stroke: "purple",
strokeOpacity: 0.5,
sort: { y: "r", reverse: true, limit: 10 }
}
)
),
Plot.ruleX(monthRules, {
strokeOpacity: 0.4
}),
Plot.image(artistData, {
x: d3.min(artistData.map((d) => d.date)),
y: (d) => d.track.name,
src: (d) => d.track.album.imageUrl,
dx: -20
})
]
})
Insert cell
albumTracksPlot("Degeneración Nacional")
Insert cell
albumTracksPlot("De Lo Más Solicitado")
Insert cell
albumTracksPlot("Homie Alone")
Insert cell
albumTracksPlot("Huracán")
Insert cell
albumTracksPlot("Música Prohibida")
Insert cell
Insert cell
Plot.plot({
width,
marginLeft: 100,
y: { domain: top5 },
padding: 0,
marks: [
Plot.frame(),
Plot.tickX(
top5Data,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "time",
y: (d) => d.track.artists[0].name,
thresholds: d3.timeMinute
}
)
)
]
})
Insert cell
Plot.plot({
width,
x: { ticks: 2, line: true },
facet: { data: top5Data, x: "day" },
fx: { domain: domains.day, label: "", line: true },
y: { domain: top5 },
marginLeft: 100,
padding: 0,
marks: [
Plot.frame(),
Plot.tickX(
top5Data,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "time",
y: (d) => d.track.artists[0].name,
thresholds: d3.timeMinute
// stroke: "timeOfDay"
}
)
)
]
})
Insert cell
Plot.plot({
width,
padding: 0,
y: { domain: top5 },
marginLeft: 100,
marks: [
Plot.frame(),
Plot.tickX(
top5Data,
Plot.binX(
{ strokeOpacity: "count" },
{
x: "playedAt",
y: (d) => d.track.artists[0].name,
thresholds: d3.timeMinute
}
)
)
]
})
Insert cell
Plot.plot({
width,
y: { domain: domains.day, label: "" },
x: { ticks: 2 },
padding: 0.07,
facet: {
data: top5Data,
x: "month",
y: (d) => d.track.artists[0].name,
marginRight: 90,
marginLeft: 25
},
fy: { domain: top5, marginRight: 100 },
fx: { domain: domains.month, label: "" },
marks: [
Plot.frame(),
Plot.dotX(
top5Data,
Plot.binX(
{ r: "count" },
{
x: "time",
y: "day",
strokeOpacity: 0.4,
thresholds: d3.timeMinute.every(20)
}
)
)
]
})
Insert cell
allSongsChart = Plot.plot({
width,
x: { label: "", ticks: 3, inset: 20, axis: "top" },
marginLeft: 175,
marks: [
Plot.dot(
top5Data,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
y: (d) => d.track.name,
thresholds: d3.timeDay,
stroke: "purple",
strokeOpacity: 0.5,
sort: { y: "r", reverse: true, limit: 50 }
}
)
),
Plot.ruleX(monthRules, {
strokeOpacity: 0.4
}),
Plot.image(top5Data, {
x: d3.min(top5Data.map((d) => d.date)),
y: (d) => d.track.name,
src: (d) => d.track.album.imageUrl,
dx: -20
})
]
})
Insert cell
Plot.plot({
width,
x: { label: "", ticks: 3 },
y: { ticks: 6 },
facet: {
data: top5Data,
x: "month",
y: (d) => d.track.artists[0].name,
marginRight: 95,
marginLeft: 30
},
fy: { domain: top5 },
fx: { label: "", domain: domains.month },
padding: 0.05,
marks: [
Plot.frame(),
Plot.dot(
top5Data,
Plot.binY(
{ r: "count" },
{
y: "time",
x: "dayOfMonth",
thresholds: d3.timeMinute.every(30),
// stroke: "purple",
strokeOpacity: 0.5
}
)
)
// Plot.ruleX([15, 30])
]
})
Insert cell
Plot.plot({
width,
x: { label: "" },
y: { ticks: 7 },
padding: 0.05,
facet: {
data: top5Data,
y: (d) => d.track.artists[0].name,
marginRight: 100
},
fy: { domain: top5 },
marginLeft: 50,
marks: [
Plot.frame(),
Plot.dot(
top5Data,
Plot.bin(
{ r: "count" },
{
x: "playedAt",
y: "time",
thresholds: d3.timeMinute.every(15),
// stroke: "purple",
strokeOpacity: 0.5
}
)
),
Plot.ruleX(monthRules, { strokeOpacity: 0.3 })
]
})
Insert cell
Insert cell
Plot.plot({
marks: [
Plot.tickX(data, {
x: "playedAt",
strokeOpacity: 0.1
})
]
})
Insert cell
Plot.plot({
marks: [
Plot.tickX(data, {
x: "time",
strokeOpacity: 0.1
})
]
})
Insert cell
Plot.plot({
marginLeft: 100,
y: { label: "", domain: weekdays, line: true },
x: { label: "", line: true, ticks: false },
color: { scheme: "purples" },
marks: [
Plot.cell(data, Plot.group({ fill: "count" }, { x: "hour", y: "day" })),
Plot.text(data, Plot.group({ text: "count" }, { x: "hour", y: "day" }))
]
})
Insert cell
Plot.plot({
facet: { data: data, y: "month", marginLeft: 100, marginRight: 100 },
fy: { label: "", domain: months.slice(7) },
marginLeft: 100,
y: { label: "", domain: weekdays },
x: { label: "" },
color: { scheme: "purples" },
marks: [
Plot.frame(),
Plot.cell(data, Plot.group({ fill: "count" }, { x: "hour", y: "day" })),
Plot.text(data, Plot.group({ text: "count" }, { x: "hour", y: "day" }))
]
})
Insert cell
Plot.plot({
y: { label: "", domain: months.slice(7), line: true },
x: { label: "Day of Month", line: true },
marginLeft: 80,
color: { scheme: "purples" },
marks: [
Plot.cell(
data,
Plot.group({ fill: "count" }, { x: "dayOfMonth", y: "month" })
),
Plot.text(
data,
Plot.group({ text: "count" }, { x: "dayOfMonth", y: "month" })
)
]
})
Insert cell
Plot.plot({
facet: { data: data, y: "day", marginLeft: 80 },
fy: { label: "", line: true, domain: domains.day },
x: { label: "Time of Day", line: true, domain: domains.timeOfDay },
marginLeft: 80,
color: { scheme: "purples" },
padding: 0.05,
marks: [
Plot.cell(data, Plot.groupX({ fill: "proportion" }, { x: "timeOfDay" })),
Plot.text(
data,
Plot.groupX({ text: "proportion-facet" }, { x: "timeOfDay" })
)
]
})
Insert cell
Plot.plot({
facet: { data: data, y: "timeOfDay", marginLeft: 100 },
fy: { domain: domains.timeOfDay, label: "" },
marks: [
Plot.dot(
data,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
thresholds: d3.timeHour,
strokeOpacity: 0.4,
stroke: "timeOfDay"
}
)
)
]
})
Insert cell
Plot.tickX(data, { x: "playedAt", strokeOpacity: 0.1 }).plot({
facet: {
data: data,
x: "hour",
y: "day",
marginLeft: 100
},
fy: {
//domain: domains.month.slice(7),
domain: domains.day,
label: ""
},
width,
fx: { label: "" },
marks: [
Plot.frame(),
Plot.rect(
data,
Plot.groupZ({ fill: "count" }, { fill: "playedAt", fillOpacity: 0.6 })
)
],
color: {
scheme: "purples"
}
})
Insert cell
Insert cell
filtered_data = data.filter((d) => d.artistsNames.includes(selected_artist))
Insert cell
viewof selected_artist = Inputs.select(top5, { unique: true, sort: true })
Insert cell
Plot.plot({
x: { domain: domains.hour },
color: { scheme: "purples" },
padding: 0,
marks: [
Plot.frame(),
Plot.cell(
filtered_data,
Plot.groupX({ fill: "count" }, { x: "hour", stroke: "black" })
),
Plot.text(filtered_data, Plot.groupX({ text: "count" }, { x: "hour" }))
]
})
Insert cell
Plot.plot({
marks: [
Plot.dot(
filtered_data,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
thresholds: d3.timeHour,
stroke: "purple",
strokeOpacity: 0.3
}
)
)
]
})
Insert cell
Plot.plot({
x: { domain: domains.day, label: "" },
color: { scheme: "purples" },
padding: 0.05,
marks: [
Plot.cell(
filtered_data,
Plot.groupX(
{ fill: "count" },
{
x: "day"
}
)
),
Plot.text(
filtered_data,
Plot.groupX(
{ text: "proportion" },
{
x: "day"
}
)
)
]
})
Insert cell
Plot.tickX(filtered_data, { x: "playedAt", strokeOpacity: 0.3 }).plot({
facet: {
data: filtered_data,
x: "hour",
y: "day",
marginLeft: 80
},
fy: {
domain: domains.day,
label: ""
},
width,
fx: { label: "" },
marks: [
Plot.frame(),
Plot.rect(
filtered_data,
Plot.groupZ(
{ fill: "count" },
{ fill: "playedAt", fillOpacity: 0.6, thresholds: d3.timeHour }
)
)
],
color: {
scheme: "purples"
}
})
Insert cell
Plot.plot({
facet: { data: filtered_data, y: "timeOfDay", marginLeft: 100 },
fy: { domain: domains.timeOfDay, label: "" },
marks: [
Plot.dot(
filtered_data,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
thresholds: d3.timeHour,
strokeOpacity: 0.4,
stroke: "timeOfDay"
}
)
)
]
})
Insert cell
Insert cell
function albumTracksPlot(albumName) {
const albumData = data.filter((d) => d.track.album.name == albumName);
return Plot.plot({
width,
x: { label: "", ticks: 3, inset: 20 },
marginLeft: 125,
marks: [
Plot.dot(
albumData,
Plot.binX(
{ r: "count" },
{
x: "playedAt",
y: (d) => d.track.name,
z: (d) => d.track.track_number,
thresholds: d3.timeDay,
stroke: "purple",
strokeOpacity: 0.5,
sort: (d) => d.track.track_number
}
)
),
Plot.image(albumData, {
x: d3.min(albumData.map((d) => d.date)),
y: (d) => d.track.name,
src: (d) => d.track.album.imageUrl,
dx: -20
})
]
});
}
Insert cell
weekdays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
]
Insert cell
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
Insert cell
timeOfDay = [
"Late night",
"Early morning",
"Morning",
"Afternoon",
"Late afternoon",
"Evening"
]
Insert cell
hours = [...Array(24).keys()]
Insert cell
// Load the Temporal API using a Polyfill
Temporal = {
const TemporalLib = await require("@js-temporal/polyfill@0.3.0");
return TemporalLib.Temporal;
}
Insert cell
domains = ({
month: months,
day: weekdays,
timeOfDay: timeOfDay,
hour: hours
})
Insert cell
artists = data.flatMap((d) => d.artistsNames)
Insert cell
top5 = ["La Plebada", "La Banda Baston", "Fntxy", "Yoga Fire", "Aleman"]
Insert cell
monthRules = d3.timeWeek
.range(
d3.min(artistData.map((d) => d.date)),
d3.max(artistData.map((d) => d.date))
)
.map((d) => new Date(d.setUTCHours(0)))
Insert cell
Plot.plot({
y: {
grid: true
},
marks: [
Plot.lineY(
artistData,
Plot.binX(
{ y: "count" },
{
x: "playedAt",
z: (d) => d.track.name,
cumulative: true,
thresholds: d3.timeDay
}
)
),
Plot.ruleY([0])
]
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more