Skip to content

Commit 27a0bd6

Browse files
authored
Hotel bookings dashboard example (#1337)
* update chart colors, add bubble, move reusables to components * tooltips, chart color updates * remove displays for troubleshooting * edits * save edits * save changes * format date in line chart * reorder seasons in faceted histogram * move data prep to loader * minor data loader updates * update readme and config file * reorg code and add comments * Move table prep to Inputs.table options * line chart tooltip * tooltip and chart tweaks * spelling * Update examples/hotel-bookings/README.md Co-authored-by: Philippe Rivière <[email protected]> * Update examples/hotel-bookings/observablehq.config.js Co-authored-by: Philippe Rivière <[email protected]> * update DonutChart API * add to readme, config to match other examples, minor updates to dashboard * examples readme * add .webp assets * remove duplicate input-2d in readme * updates donut color and config file * update line chart tip format * testing text stroke in donuts * update big number to html from Plot * clean up donutChart component * replace bubble grid with faceted bar chart room type * update webp images to new version * remove cruft * no dot * rename variables * nicer * variable color line (reflecting season) * force the complete facet domain ref. observablehq/plot#2014 * removes data loader, use static simplified file * Clean up tooltip labels * Update examples/hotel-bookings/src/components/donutChart.js Co-authored-by: Philippe Rivière <[email protected]> * updated webp thumbnails * updates README * resize dark thumbnail * fix the color line when getting into empty bins.
1 parent 34ffa37 commit 27a0bd6

File tree

11 files changed

+40579
-0
lines changed

11 files changed

+40579
-0
lines changed

docs/assets/hotel-bookings-dark.webp

26.6 KB
Binary file not shown.

docs/assets/hotel-bookings.webp

23.4 KB
Binary file not shown.

examples/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717

1818
[Source](./eia) · This dashboard visualizes electricity generation and demand across the U.S. electricity grid. The included data loaders demonstrate how to retrieve live data from the U.S. Energy Information Administration (EIA) API, while the dashboard demonstrates how to produce interactive maps, bar charts, and time-series charts with Observable Plot. A range input allows the user to rewind time to any point in the previous 24 hours, and a table shows details.
1919

20+
### [`hotel-bookings`](https://observablehq.observablehq.cloud/framework-example-hotel-bookings) - Resort hotel bookings
21+
22+
<a href="https://observablehq.observablehq.cloud/framework-example-hotel-bookings/"><img src="../docs/assets/hotel-bookings.webp" alt="Resort hotel bookings" width="312" height="237"></a>
23+
24+
[Source](./hotel-bookings) · This dashboard visualizes hotel bookings by market segment for a Portuguese resort from 2015 to 2017. The dashboard demonstrates how to produce interactive charts with D3 and Observable Plot, either created as JavaScript components or directly within a markdown file. Donut charts, histograms, a bubble chart and a line chart reveal patterns in room bookings, prices, guest nationalities, and cancellations across different market segments and seasons.
25+
2026
### [`plot`](https://observablehq.observablehq.cloud/framework-example-plot/) - Observable Plot downloads
2127

2228
<a href="https://observablehq.observablehq.cloud/framework-example-plot/"><img src="../docs/assets/plot.webp" alt="Observable Plot downloads" width="312" height="237"></a>

examples/hotel-bookings/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
/dist/
3+
node_modules/
4+
yarn-error.log

examples/hotel-bookings/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[Framework examples →](../)
2+
3+
# Hotel bookings
4+
5+
View live: <https://observablehq.observablehq.cloud/framework-example-hotel-bookings/>
6+
7+
This dashboard explores reservation data for a resort in Portugal from Antonio _et al._ (2021), recorded between July 2015 and August 2017. Charts highlight differences in daily rates, visit seasons, guest nationality, and room type reserved for different booking types (_e.g._ corporate, direct, or online reservations).
8+
9+
**Data source:** Antonio et al (2021). Hotel booking demand datasets. Data in Brief (22): 41-49. https://doi.org/10.1016/j.dib.2018.11.126
10+
11+
## Implementation
12+
13+
```
14+
.
15+
├── README.md
16+
├── observablehq.config.js
17+
├── package.json
18+
└── src
19+
├── components
20+
│ ├── bigNumber.js
21+
│ └── donutChart.js
22+
├── data
23+
│ ├── hotelData.csv
24+
└── index.md
25+
```
26+
27+
No dependencies other than Framework.
28+
29+
### Data
30+
31+
Data was downloaded directly from Antonio _et al._ (2021), with minor data wrangling to produce the simplified data (`hotelData.csv`) used in the dashboard.
32+
33+
### Components
34+
35+
This example has two reusable components for building the visualizations: `bigNumber.js` and `donutChart.js`. The `bigNumber.js` component creates a simple big number box. The `donutChart.js` component is made with [D3](https://d3js.org/).
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
root: "src"
3+
};

examples/hotel-bookings/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"type": "module",
3+
"private": true,
4+
"scripts": {
5+
"clean": "rimraf src/.observablehq/cache",
6+
"build": "rimraf dist && observable build",
7+
"dev": "observable preview",
8+
"deploy": "observable deploy",
9+
"observable": "observable"
10+
},
11+
"dependencies": {
12+
"@observablehq/framework": "latest",
13+
"d3-dsv": "^3.0.1",
14+
"d3-time-format": "^4.1.0"
15+
},
16+
"devDependencies": {
17+
"rimraf": "^5.0.5"
18+
},
19+
"engines": {
20+
"node": ">=18"
21+
}
22+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {html} from "npm:htl";
2+
3+
export function bigNumber(metric, dateArray, value, compare, width) {
4+
return html`
5+
<h2>${metric}</h2>
6+
<div style="font-size: ${width / 250}rem">
7+
<h3><i>${dateArray[0]} to ${dateArray[1]}</i></h3>
8+
<h1>${value}</h1>
9+
<div>${compare}</div>
10+
<div>`;
11+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// https://observablehq.com/@d3/donut-chart/2
2+
3+
import * as d3 from "npm:d3";
4+
5+
export function DonutChart(data, {centerText, width, colorDomain, colorRange}) {
6+
const height = width;
7+
const radius = Math.min(width, height) / 2;
8+
const arc = d3
9+
.arc()
10+
.innerRadius(radius * 0.6)
11+
.outerRadius(radius - 1);
12+
13+
const pie = d3
14+
.pie()
15+
.padAngle(2 / radius)
16+
.sort(null)
17+
.value((d) => d.value);
18+
19+
const color = d3.scaleOrdinal().domain(colorDomain).range(colorRange);
20+
21+
const svg = d3
22+
.create("svg")
23+
.attr("width", width)
24+
.attr("height", height)
25+
.attr("viewBox", [-width / 2, -height / 2, width, height])
26+
.attr("style", "max-width: 100%; height: auto;");
27+
28+
svg
29+
.append("g")
30+
.selectAll()
31+
.data(pie(data))
32+
.join("path")
33+
.attr("fill", (d) => color(d.data.name))
34+
.attr("d", arc);
35+
36+
svg
37+
.append("g")
38+
.attr("font-family", "sans-serif")
39+
.attr("font-size", 10)
40+
.attr("text-anchor", "middle")
41+
.selectAll()
42+
.data(pie(data))
43+
.join("text")
44+
.attr("transform", (d) => `translate(${arc.centroid(d)})`)
45+
.call((text) =>
46+
text
47+
.append("tspan")
48+
.filter((d) => d.endAngle - d.startAngle > 0.1)
49+
.attr("y", "-0.3em")
50+
.attr("font-weight", "bold")
51+
.attr("fill", "white")
52+
.text((d) => d.data.name)
53+
)
54+
.call((text) =>
55+
text
56+
.filter((d) => d.endAngle - d.startAngle > 0.15)
57+
.append("tspan")
58+
.attr("x", 0)
59+
.attr("y", "0.8em")
60+
.attr("fill", "white")
61+
.attr("fill-opacity", 1)
62+
.text((d) => d.data.value.toLocaleString("en-US"))
63+
);
64+
65+
svg
66+
.append("text")
67+
.attr("text-anchor", "middle")
68+
.attr("font-family", "sans-serif")
69+
.attr("font-size", "0.9rem")
70+
.attr("fill", "currentColor")
71+
.attr("font-weight", 600)
72+
.text(centerText);
73+
74+
return svg.node();
75+
}

0 commit comments

Comments
 (0)