-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.html
228 lines (205 loc) · 8.07 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><link rel="icon" href="data:">
<title>小浜エリア 宿泊予約状況(データ出典:福井県観光連盟)</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</head><body>
<h1>小浜エリア 宿泊予約状況(データ出典:福井県観光連盟)</h1>
期間:<input type="date" id=fromDate min="2023-10-01" />~<input type="date" id=toDate min="2023-10-01" />
<div id=chartContent class="tab-pane fade show active">
<div id="chart"></div>
</div>
<script type="module">
import { CSV } from "https://js.sabae.cc/CSV.js";
import { Day } from "https://js.sabae.cc/DateTime.js";
import { TimeZone } from "https://code4fukui.github.io/day-es/DateTime.js";
import { initC3 } from "https://code4fukui.github.io/c3-es/c3-es.js";
const c3 = initC3(window);
fromDate.setAttribute("value", new Day().toString());
toDate.setAttribute("value", new Day().dayAfter(89).toString());
const hotelData = CSV.toJSON(await CSV.fetch("./latest_hotel.csv"));
totalHotelCount.textContent = hotelData.length;
totalRoomCount.textContent = hotelData.reduce((sum, h) => {
return sum + parseInt(h.nrooms);
}, 0);
const getChartFile = async (targetDate) => {
const url = "./latest_rsv_sum.csv";
const csv = await CSV.fetch(url);
const map = {
"n_room": "室数",
"n_people": "利用総人数",
"n_stay": "平均泊数",
"amount_fee": "利用金額合計",
"n_reserve": "予約件数",
"date_visit": "利用開始日",
};
for (let i = 0; i < csv[0].length; i++) {
csv[0][i] = map[csv[0][i]];
}
return CSV.toJSON(csv);
};
const makeChartColumns = (data, startday, endday, colnames) => {
const dates = [];
for (let d = startday; d.includes(startday, endday); d = d.dayAfter(1)) {
dates.push(d.toStringYMD());
}
dates.unshift("x");
const cols = [];
cols.push(dates);
const targetData = data.filter(d => {
const targetDate = new Day(d.利用開始日);
return targetDate.includes(startday, endday);
});
for (let j = 0; j < colnames.length; j++) {
const colname = colnames[j];
const nrsv = Array(dates.length - 1);
nrsv.fill(0);
for (const d of targetData) {
const targetDate = new Day(d.利用開始日);
const startMsec = new Date(startday.year, startday.month - 1, startday.day);
const targetMsec = new Date(targetDate.year, targetDate.month - 1, targetDate.day);
const diffDay = parseInt((targetMsec - startMsec) / 1000 / 60 / 60 / 24);
nrsv[diffDay] = parseInt(d[colname]);
}
nrsv.unshift(colname);
cols.push(nrsv);
}
// 平均泊数
for (let i = 1 ; i < cols[3].length ; i++) {
if (!targetData[i - 1] || parseInt(targetData[i - 1]["予約件数"]) == 0) {
continue;
}
cols[3][i] = (parseInt(cols[3][i]) / parseInt(targetData[i - 1]["予約件数"])).toFixed(2);
}
return cols;
};
const NAME_FEE = "利用金額合計";
const NAME_OCC = "客室稼働率 (OCC)";
const NAME_ADR = "客室平均単価 (ADR)";
const NAME_AVE = "客平均単価";
const NAME_REVPAR = "RevPAR (OCC x ADR)";
const showChart = (bindto, columns) => {
const axes = {};
for (const n of [NAME_FEE, NAME_AVE, NAME_ADR, NAME_REVPAR]) {
axes[n] = "y2";
}
const chart = c3.generate({
bindto: bindto,
data: {
x: 'x',
xFormat: '%Y%m%d', // 'xFormat' can be used as custom format of 'x'
columns,
axes,
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m-%d'
}
},
y2: {
show: true,
},
},
tooltip: {
format: {
value: (value) => d3.format(",")(value),
},
// nullを設定するとデータのカラム順に表示される
order: null
},
regions: [
{
axis: "x",
end: new Date(new Day().prevDay().toString()),
class: "regionX"
}
]
});
};
const chartData = await getChartFile();
const show = async (startDate, endDate) => {
if (!startDate || !endDate) {
return;
}
const colnames = ["利用総人数", "室数", "平均泊数", NAME_FEE];
const startday = new Day(startDate);
const endday = new Day(endDate);
const columns = makeChartColumns(chartData, startday, endday, colnames);
const occ = [NAME_OCC];
const adr = [NAME_ADR];
const adr2 = [NAME_AVE];
const revpar = [NAME_REVPAR];
for (let i = 1; i < columns[0].length; i++) {
// columns[0] // date
const people = columns[1]; // 人数
const room = columns[2]; // 室数
const amount = columns[4]; // 利用金額合計
if (room[i] == 0) {
occ[i] = "";
adr[i] = "";
adr2[i] = "";
revpar[i] = "";
} else {
const capacity = hotelData.filter(h => {
return new Date(h.create_file_begin_date) <= new Date(new Day(columns[0][i]).toString());
}).reduce((sum, h) => {
return sum + parseInt(h.nrooms);
}, 0);
occ[i] = (parseInt(room[i]) / capacity * 100).toFixed(1);
adr[i] = (parseInt(amount[i]) / parseInt(room[i])).toFixed(0);
adr2[i] = (parseInt(amount[i]) / parseInt(people[i])).toFixed(0);
revpar[i] = (occ[i] / 100 * adr[i]).toFixed(0);
}
}
columns.push(adr2);
columns.push(occ);
columns.push(adr);
columns.push(revpar);
showChart("#chart", columns);
const today = new Day(TimeZone.JST);
const spanDateElements = document.getElementsByClassName("span-date");
Array.from(spanDateElements).forEach(element => {
element.innerText = `${today.year}年${today.month}月${today.day}日`;
});
};
fromDate.onchange = () => {
show(fromDate.value, toDate.value);
};
toDate.onchange = () => {
show(fromDate.value, toDate.value);
};
show(fromDate.value, toDate.value);
</script>
<div class=caption>
福井県小浜エリア、<span id="totalHotelCount"></span>のホテル(総客室数<span id="totalRoomCount"></span>)の予約状況を合算したオープンデータです。(部屋数は<span class="span-date"></span>時点です)<br>
「<a href=latest_rsv_sum.csv>CSVオープンデータ ダウンロード</a>」(<span class="span-date"></span>現在)<br>
<br>
客室稼働率 OCC = Occupancy Ratio = 予約客室数 / 総客室数<br>
客室平均単価 ADR = Average Daily Rate = 販売額 / 予約客室数<br>
RevPAR = Revenue Per Available Rooms = OCC x ADR<br>
</div>
<a href=https://code4fukui.github.io/fukui-kanko-advice/area/52.html>福井県AI観光アドバイス - 小浜市 / 小浜 エリア(宿泊施設)</a><br>
<br>
<a href=https://code4fukui.github.io/fukui-kanko-stat/>福井県観光アンケートオープンデータ活用アプリ</a><br>
<hr>
データ出典: <a href=https://www.fuku-e.com/feature/detail_266.html>福井県観光データ分析システム「FTAS」|特集|【公式】福井県 観光/旅行サイト | ふくいドットコム</a> by <a href=https://www.fuku-e.com/>福井県観光連盟</a><br>
データ収集&アプリ: <a href=https://github.com/code4fukui/fukui-station-kanko-reservation/>オープンソース on GitHub</a> by <a href=https://code4fukui.github.io/>Code for FUKUI</a><br>
<style>
body {
text-align: center;
}
#chart {
height: 50vh;
}
.caption {
margin: 1em;
}
a {
color: gray !important;
}
.c3-region.regionX {
fill: blue;
}
</style>
</body></html>