dashboard = (async () => {
const wk = await FileAttachment("amsterdam_weekdays.csv").csv({typed:true})
const we = await FileAttachment("amsterdam_weekends.csv").csv({typed:true})
const raw = [
...wk.map(d => ({ ...d, day_type: "Weekday" })),
...we.map(d => ({ ...d, day_type: "Weekend" }))
]
const data = raw.map(d => ({
...d,
price: +d.realSum,
guest_satisfaction_overall: +d.guest_satisfaction_overall,
cleanliness_rating: +d.cleanliness_rating
}))
const dayTypes = ["Weekday", "Weekend"]
const roomTypes = Array.from(new Set(data.map(d=>d.room_type))).sort()
const metrics = ["guest_satisfaction_overall", "cleanliness_rating"]
const metricLabels = {
guest_satisfaction_overall: "Guest Satisfaction",
cleanliness_rating: "Cleanliness"
}
const daySelect = Inputs.select(dayTypes, {label:"Day Type", value:dayTypes[0]})
const roomSelect = Inputs.select(roomTypes, {label:"Room Type", value:roomTypes[0]})
const xSelect = Inputs.select(metrics, {label:"X-Axis Metric", value:metrics[0]})
const container = html`<div style="font-family:Arial,sans-serif; margin:1rem;"></div>`
const controls = html`<div style="display:flex; gap:1rem; flex-wrap:wrap; margin-bottom:1rem;"></div>`
controls.append(daySelect, roomSelect, xSelect)
container.append(controls)
const chartDiv = html`<div style="background:#fafafa; padding:1rem; border-radius:8px;"></div>`
container.append(chartDiv)
// 4) Render
function render() {
const metric = xSelect.value
const filtered = data.filter(d =>
d.day_type === daySelect.value &&
d.room_type === roomSelect.value
)
const avg = d3.rollups(
filtered,
v => d3.mean(v, d=>d.price),
d => d[metric]
).map(([xVal, avgPrice]) => ({ xVal:+xVal, avgPrice }))
.sort((a,b)=>d3.ascending(a.xVal,b.xVal))
chartDiv.innerHTML = ""
if (!avg.length) {
chartDiv.textContent = "No data for those settings"
return
}
const plot = Plot.plot({
width: 800,
height: 400,
marginLeft: 70,
marginRight: 20,
marginBottom: 60,
grid: true,
style: {
background: "#ffffff",
color: "#333",
fontSize: "12px"
},
x: {
label: metricLabels[metric],
tickRotate: -45,
tickSize: 5,
domain: avg.map(d=>d.xVal),
grid: false
},
y: {
label: "Avg. Price (€)",
grid: true
},
marks: [
// area with monotone curve
Plot.areaY(avg, {
x: "xVal", y: "avgPrice",
fill: "#cee2f2",
curve: d3.curveMonotoneX
}),
// bars
Plot.barY(avg, {
x: "xVal", y: "avgPrice",
fill: "#6a9fb5",
inset: 0.2
}),
// line
Plot.line(avg, {
x: "xVal", y: "avgPrice",
stroke: "#1f78b4",
strokeWidth: 2,
curve: d3.curveMonotoneX
}),
// interactive dots with tooltip
Plot.dot(avg, {
x: "xVal", y: "avgPrice",
fill: "#e31a1c",
r: 5,
title: d => `${metricLabels[metric]} ${d.xVal}\nAvg Price €${d.avgPrice.toFixed(2)}`
})
]
})
chartDiv.append(plot)
}
// 5) Listeners & initial draw
for (const el of [daySelect, roomSelect, xSelect]) el.addEventListener("input", render)
render()
return container
})()