-
Notifications
You must be signed in to change notification settings - Fork 177
Hotel bookings dashboard example #1337
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 18 commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
d642e6f
update chart colors, add bubble, move reusables to components
894e278
tooltips, chart color updates
75f1590
remove displays for troubleshooting
a3643f7
Merge branch 'main' into allison/hotel-example
f15215e
edits
6922b49
save edits
6843890
save changes
fcf9cf4
format date in line chart
d7b4c44
reorder seasons in faceted histogram
29a9bcd
move data prep to loader
34556c2
minor data loader updates
f0fbede
update readme and config file
778d6fd
Merge branch 'main' into allison/hotel-example
169845b
reorg code and add comments
3fd2a2a
Move table prep to Inputs.table options
57382e4
line chart tooltip
d678a45
tooltip and chart tweaks
6a412cc
spelling
4679321
Update examples/hotel-bookings/README.md
allisonhorst 4df2462
Update examples/hotel-bookings/observablehq.config.js
allisonhorst e9f47a5
Merge branch 'main' into allison/hotel-example
69249fe
update DonutChart API
72af691
add to readme, config to match other examples, minor updates to dashb…
allisonhorst 7e43e26
examples readme
allisonhorst 5c5940c
readme
allisonhorst abe695f
add .webp assets
allisonhorst c5d8f8a
remove duplicate input-2d in readme
allisonhorst 7d71bb1
updates donut color and config file
allisonhorst fada07d
Merge branch 'main' into allison/hotel-example
allisonhorst dfcd5c6
update line chart tip format
allisonhorst f72bbd0
testing text stroke in donuts
allisonhorst c15030d
update big number to html from Plot
allisonhorst 1c7c841
clean up donutChart component
allisonhorst 7014c82
replace bubble grid with faceted bar chart room type
allisonhorst 9ae7954
Merge branch 'main' into allison/hotel-example
allisonhorst f436c4d
Merge branch 'main' into allison/hotel-example
allisonhorst 79ec11a
Merge branch 'allison/hotel-example' of https://github.com/observable…
allisonhorst 3ecd0f1
update webp images to new version
allisonhorst cac8e6a
Merge branch 'main' into allison/hotel-example
Fil 803ffd9
remove cruft
Fil 02361d7
no dot
Fil f077328
rename variables
Fil 3d1a876
nicer
Fil 4df64b7
variable color line (reflecting season)
Fil 14b5407
force the complete facet domain
Fil 63dd5dc
removes data loader, use static simplified file
allisonhorst b363bf4
Clean up tooltip labels
allisonhorst 7000221
Update examples/hotel-bookings/src/components/donutChart.js
allisonhorst a4d202b
updated webp thumbnails
allisonhorst 099d4de
updates README
allisonhorst 21fa922
resize dark thumbnail
allisonhorst 1cea26b
fix the color line when getting into empty bins.
Fil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .DS_Store | ||
| /dist/ | ||
| node_modules/ | ||
| yarn-error.log |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| [Framework examples →](../) | ||
|
|
||
| # Hotel bookings | ||
|
|
||
| View live: <https://observablehq.observablehq.cloud/framework-example-hotel-bookings/> | ||
|
|
||
| This dashboard explores reservation data for a resort in Portugal from Antonio _et al._ (2021), recorded between July 2015 and August 2017 to highlight differences in daily rates, visit seasons, guest nationality, and room type reserved for different booking types (_e.g._ corporate, direct, or online reservations). | ||
|
|
||
| **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 | ||
|
|
||
| ## Implementation | ||
|
|
||
| ``` | ||
| . | ||
| ├── README.md | ||
| ├── observablehq.config.js | ||
| ├── package.json | ||
| └── src | ||
| ├── components | ||
| │ ├── bigNumber.js | ||
| │ └── donutChart.js | ||
| ├── data | ||
| │ ├── hotelData.csv.js | ||
| │ └── hotels.csv | ||
| └── index.md | ||
| ``` | ||
|
|
||
| No dependencies other than Framework. No required configuration (static data). The project config only adds our example header and example layout. | ||
allisonhorst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### Data | ||
|
|
||
| Data in `hotels.csv` was downloaded directly from Antonio _et al._ (2021), then pre-processed in the `hotelData.csv.js` data loader to emit the static `hotelData.csv` file used in the dashboard. | ||
|
|
||
| ### Components | ||
|
|
||
| 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 using [Observable Plot](https://observablehq.com/plot/). The `donutChart.js` component is made with [D3](https://d3js.org/). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| export default { | ||
| root: "src", | ||
|
|
||
| // Shared Observable example configuration; feel free to remove this. | ||
| title: "Observable Framework", | ||
| pager: false, | ||
| toc: false, | ||
| sidebar: false, | ||
| head: | ||
| process.env.CI && | ||
| `<script type="module" async src="https://events.observablehq.com/client.js?pageLoad"></script> | ||
| <script async src="https://www.googletagmanager.com/gtag/js?id=G-9B88TP6PKQ"></script> | ||
| <script>window.dataLayer=window.dataLayer||[];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js',new Date());\ngtag('config','G-9B88TP6PKQ');</script>`, | ||
| header: `<style> | ||
|
|
||
| #observablehq-header a[href] { | ||
| color: inherit; | ||
| } | ||
|
|
||
| #observablehq-header a[target="_blank"] { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 0.25rem; | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| #observablehq-header a[target="_blank"]:hover span { | ||
| text-decoration: underline; | ||
| } | ||
|
|
||
| #observablehq-header a[target="_blank"]::after { | ||
| content: "\\2197"; | ||
| } | ||
|
|
||
| #observablehq-header a[target="_blank"]:not(:hover, :focus)::after { | ||
| color: var(--theme-foreground-muted); | ||
| } | ||
|
|
||
| @container not (min-width: 640px) { | ||
| .hide-if-small { | ||
| display: none; | ||
| } | ||
| } | ||
|
|
||
| </style> | ||
| <div style="display: flex; align-items: center; gap: 0.5rem; height: 2.2rem; margin: -1.5rem -2rem 2rem -2rem; padding: 0.5rem 2rem; border-bottom: solid 1px var(--theme-foreground-faintest); font: 500 16px var(--sans-serif);"> | ||
| <a href="https://observablehq.com/" target="_self" rel="" style="display: flex; align-items: center;"> | ||
| <svg width="22" height="22" viewBox="0 0 21.92930030822754 22.68549919128418" fill="currentColor"> | ||
| <path d="M10.9646 18.9046C9.95224 18.9046 9.07507 18.6853 8.33313 18.2467C7.59386 17.8098 7.0028 17.1909 6.62722 16.4604C6.22789 15.7003 5.93558 14.8965 5.75735 14.0684C5.56825 13.1704 5.47613 12.2574 5.48232 11.3427C5.48232 10.6185 5.52984 9.92616 5.62578 9.26408C5.7208 8.60284 5.89715 7.93067 6.15391 7.24843C6.41066 6.56618 6.74143 5.97468 7.14438 5.47308C7.56389 4.9592 8.1063 4.54092 8.72969 4.25059C9.38391 3.93719 10.1277 3.78091 10.9646 3.78091C11.977 3.78091 12.8542 4.00021 13.5962 4.43879C14.3354 4.87564 14.9265 5.49454 15.3021 6.22506C15.6986 6.97704 15.9883 7.7744 16.1719 8.61712C16.3547 9.459 16.447 10.3681 16.447 11.3427C16.447 12.067 16.3995 12.7593 16.3035 13.4214C16.2013 14.1088 16.0206 14.7844 15.7644 15.437C15.4994 16.1193 15.1705 16.7108 14.7739 17.2124C14.3774 17.714 13.8529 18.1215 13.1996 18.4349C12.5463 18.7483 11.8016 18.9046 10.9646 18.9046ZM12.8999 13.3447C13.4242 12.8211 13.7159 12.0966 13.7058 11.3427C13.7058 10.5639 13.4436 9.89654 12.92 9.34074C12.3955 8.78495 11.7441 8.50705 10.9646 8.50705C10.1852 8.50705 9.53376 8.78495 9.00928 9.34074C8.49569 9.87018 8.21207 10.5928 8.22348 11.3427C8.22348 12.1216 8.48572 12.7889 9.00928 13.3447C9.53376 13.9005 10.1852 14.1784 10.9646 14.1784C11.7441 14.1784 12.3891 13.9005 12.8999 13.3447ZM10.9646 22.6855C17.0199 22.6855 21.9293 17.6068 21.9293 11.3427C21.9293 5.07871 17.0199 0 10.9646 0C4.90942 0 0 5.07871 0 11.3427C0 17.6068 4.90942 22.6855 10.9646 22.6855Z"></path> | ||
| </svg> | ||
| </a> | ||
| <div style="display: flex; flex-grow: 1; justify-content: space-between; align-items: baseline;"> | ||
| <a href="https://observablehq.com/framework/" target="_self" rel=""> | ||
| <span class="hide-if-small">Observable</span> Framework | ||
| </a> | ||
| <span style="display: flex; align-items: baseline; gap: 0.5rem; font-size: 14px;"> | ||
| <a target="_blank" href="https://github.com/observablehq/framework/tree/main/examples/hotel-bookings"><span>View source</span></a> | ||
| </span> | ||
| </div> | ||
| </div>` | ||
allisonhorst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "type": "module", | ||
| "private": true, | ||
| "scripts": { | ||
| "clean": "rimraf src/.observablehq/cache", | ||
| "build": "rimraf dist && observable build", | ||
| "dev": "observable preview", | ||
| "deploy": "observable deploy", | ||
| "observable": "observable" | ||
| }, | ||
| "dependencies": { | ||
| "@observablehq/framework": "latest", | ||
| "d3-dsv": "^3.0.1", | ||
| "d3-time-format": "^4.1.0" | ||
| }, | ||
| "devDependencies": { | ||
| "rimraf": "^5.0.5" | ||
| }, | ||
| "engines": { | ||
| "node": ">=18" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import * as Plot from "npm:@observablehq/plot"; | ||
|
|
||
| export function bigNumber(metric, dateArray, value, compare, width) { | ||
| return Plot.plot({ | ||
| width, | ||
| height: 250, | ||
| marks: [ | ||
| Plot.text([metric], { | ||
| frameAnchor: "left", | ||
| fontSize: 30, | ||
| dy: -100, | ||
| opacity: 0.8, | ||
| fontWeight: 800 | ||
| }), | ||
| Plot.text([dateArray], { | ||
| frameAnchor: "left", | ||
| fontSize: 30, | ||
| fontStyle: "italic", | ||
| text: (d) => `${d[0]} to ${d[1]}`, | ||
| dy: -50 | ||
| }), | ||
| Plot.text([value], { | ||
| frameAnchor: "left", | ||
| fontSize: 80, | ||
| fontWeight: 800, | ||
| dy: 30 | ||
| }), | ||
| Plot.text([compare], { | ||
| frameAnchor: "left", | ||
| fontSize: 30, | ||
| dy: 120 | ||
| }) | ||
| ] | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| // Code adapted (barely) from Mike Bostock's Donut chart notebook (https://observablehq.com/@d3/donut-chart/2) | ||
|
|
||
allisonhorst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import * as d3 from "npm:d3"; | ||
|
|
||
| export function donutChart(data, centerText, width, colorScale) { | ||
allisonhorst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const height = width; | ||
| const radius = Math.min(width, height) / 2; | ||
|
|
||
| const arc = d3 | ||
| .arc() | ||
| .innerRadius(radius * 0.6) | ||
| .outerRadius(radius - 1); | ||
|
|
||
| const pie = d3 | ||
| .pie() | ||
| .padAngle(2 / radius) | ||
| .sort(null) | ||
| .value((d) => d.value); | ||
|
|
||
| const color = d3 | ||
| .scaleOrdinal() | ||
| .domain(data.map((d) => d.name)) | ||
| .range(colorScale); | ||
|
|
||
| const svg = d3 | ||
| .create("svg") | ||
| .attr("width", width) | ||
| .attr("height", height) | ||
| .attr("viewBox", [-width / 2, -height / 2, width, height]) | ||
| .attr("style", "max-width: 100%; height: auto;"); | ||
|
|
||
| svg | ||
| .append("g") | ||
| .selectAll() | ||
| .data(pie(data)) | ||
| .join("path") | ||
| .attr("fill", (d) => color(d.data.name)) | ||
| //.attr("stroke", "currentColor") | ||
| .attr("d", arc); | ||
|
|
||
| svg | ||
| .append("g") | ||
| .attr("font-family", "sans-serif") | ||
| .attr("font-size", 10) | ||
| .attr("text-anchor", "middle") | ||
| .selectAll() | ||
| .data(pie(data)) | ||
| .join("text") | ||
| .attr("transform", (d) => `translate(${arc.centroid(d)})`) | ||
| .call((text) => | ||
| text | ||
| .append("tspan") | ||
| .filter((d) => d.endAngle - d.startAngle > 0.1) | ||
| .attr("y", "-0.3em") | ||
| .attr("font-weight", "bold") | ||
| .attr("fill", "white") | ||
| .text((d) => d.data.name) | ||
| ) | ||
| .call((text) => | ||
| text | ||
| .filter((d) => d.endAngle - d.startAngle > 0.15) | ||
| .append("tspan") | ||
| .attr("x", 0) | ||
| .attr("y", "0.8em") | ||
| .attr("fill", "white") | ||
| .attr("fill-opacity", 1) | ||
| .text((d) => d.data.value.toLocaleString("en-US")) | ||
| ); | ||
|
|
||
| svg | ||
| .append("text") | ||
| .attr("text-anchor", "middle") | ||
| .attr("font-family", "sans-serif") | ||
| .attr("font-size", "0.9rem") | ||
| .attr("fill", "currentColor") | ||
| .attr("font-weight", 600) | ||
| .text(centerText); | ||
|
|
||
| return svg.node(); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import {csvParse} from "d3-dsv"; | ||
| import {csvFormat} from "d3-dsv"; | ||
| import {timeParse} from "d3-time-format"; | ||
| import {readFileSync} from "fs"; | ||
| //import {FileAttachment} from "npm:@observablehq/stdlib"; | ||
|
|
||
| const hotels = await csvParse(readFileSync("src/data/hotels.csv", "utf8")); | ||
|
|
||
| const hotelData = hotels.map((d, i) => ({ | ||
| ...d, | ||
| IsCanceled: d.IsCanceled == 0 ? "Keep" : "Cancel", | ||
| season: ["June", "July", "August"].includes(d.ArrivalDateMonth) | ||
| ? "Summer" | ||
| : ["September", "October", "November"].includes(d.ArrivalDateMonth) | ||
| ? "Fall" | ||
| : ["December", "January", "February"].includes(d.ArrivalDateMonth) | ||
| ? "Winter" | ||
| : "Spring", | ||
| Country: d.Country == "NULL" ? "Unknown" : d.Country, | ||
| Meal: d.Meal.trim(), | ||
| ReservedRoomType: d.ReservedRoomType.trim(), | ||
| AssignedRoomType: d.AssignedRoomType.trim(), | ||
| MarketSegment: | ||
| d.MarketSegment == "Online TA" | ||
| ? "Online travel agent" | ||
| : d.MarketSegment == "Offline TA/TO" | ||
| ? "Offline travel agent / tour operator" | ||
| : d.MarketSegment, | ||
| arrivalDate: timeParse("%B %d, %Y")(d.ArrivalDateMonth + " " + d.ArrivalDateDayOfMonth + ", " + d.ArrivalDateYear) | ||
| })); | ||
|
|
||
| process.stdout.write(csvFormat(hotelData)); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.