-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.html
226 lines (206 loc) · 10.8 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
<!-- https://observablehq.com/@d3/bubble-chart -->
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="description" content="Bubble Charts for Last.Fm">
<title>bubble charts for last.fm</title>
<!-- Using bulma for styling -->
<link rel="stylesheet" type="text/css" href="node_modules/bulma/css/bulma.min.css" />
<link rel="stylesheet" type="text/css" href="node_modules/bulma-switch/dist/css/bulma-switch.min.css" />
<link rel="stylesheet" type="text/css" href="node_modules/bulma-divider/dist/css/bulma-divider.min.css" />
<link rel="stylesheet" type="text/css" href="node_modules/@mdi/font/css/materialdesignicons.min.css" />
</head>
<style>
html,
body {
height: 100%;
}
/* Vertical flexbox container no larger than screen height */
.container {
display: flex;
flex-flow: column;
height: 100vh;
}
/* Footer margins */
#footer {
margin: 0.5em 0.5em
}
/* Horizontal flexbox for control elements, centered */
#controls {
display: flex;
align-items: center;
padding: 0 1em;
justify-content: center;
}
/* Spacing between each control element */
#controls>* {
margin: 0 0.5em;
}
/* The svg will fill the remaining height in browser (flex: 1) and is horizontally centered. */
#svgcontainer {
display: flex;
flex-flow: column;
flex: 1;
width: fit-content;
align-self: center;
justify-content: center;
}
/* Ensure the svg does not exceed the size of it's container */
svg {
flex-shrink: 1;
}
/* Used to hide either the title or svg depending on state */
.hidden {
display: none;
}
</style>
<a href="https://github.com/daniel-stoneuk/bubblechartslastfm" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
<body>
<div class="container">
<!-- Options for the chart, shown at the top of the screen, horizontal -->
<div id="controls">
<!-- Username input with my username as placeholder -->
<p><a href="https://www.last.fm">Last.fm</a> username:</p>
<input style="width:150px" id="username" class="input is-small is-rounded" type="text"
placeholder="danster103">
<!-- Buttons for either album or artist - using the same callback. -->
<button class="button is-small is-info is-rounded" onclick="buttonHandler(this, 'albums')">Top
Albums</button>
<button class="button is-small is-info is-light is-rounded"
onclick="buttonHandler(this, 'artists')">Artists</button>
<!-- Divider -->
<div class="is-divider-vertical" data-content="OPTIONS"></div>
<!-- Selects for choosing period and number of items -->
<div class="select is-rounded is-small">
<select id="periodSelect">
<option value="overall" selected>Overall</option>
<option value="7day">7 days</option>
<option value="1month">1 month</option>
<option value="3month">3 months</option>
<option value="6month">6 months</option>
<option value="12month">12 months</option>
</select>
</div>
<div class="select is-rounded is-small">
<select id="limitSelect">
<option value="50">Top 50</option>
<option value="100">Top 100</option>
<option value="200" selected>Top 200</option>
<option value="400">Top 400</option>
</select>
</div>
<!-- Switch to enable / disable album art, initally on -->
<div class="field">
<input id="albumArtSwitch" type="checkbox" name="albumArtSwitch" class="switch is-rounded is-info"
onchange="albumArtHandler(this)" checked="checked">
<label for="albumArtSwitch">Album Art</label>
</div>
<button class="button is-rounded is-small" onclick="shuffleHandler(this)">
<span class="icon is-small">
<i class="mdi mdi-shuffle"></i>
</span>
</button>
</div>
<!-- Svg container fills the remaining height of the screen -->
<div id="svgcontainer">
<!-- Horizontally and vertically centered in available space -->
<div id="title">
<h1 class="title is-1 has-text-centered">bubble charts for last.fm</h1>
<p class="subtitle has-text-centered is-4">enter a <a href="https://last.fm">last.fm</a> username
above.</p>
</div>
<!-- Fills entire space once title is hidden -->
<svg height="100%" class="hidden" id="bubbleChart"></svg>
</div>
<!-- Footer is just a simple h6 subtitle tag -->
<div id="footer">
<h6 class="subtitle is-6 is-marginless has-text-centered">bubble charts for last.fm by <a href="https://danstone.uk">Dan Stone</a>. created with <a href="https://d3js.org/">d3.js</a>, based on <a
href="https://observablehq.com/@d3/bubble-chart">Bubble Chart by Mike Bostock</a>. <strong>best
viewed on desktop.</strong></h6>
<p class="has-text-grey is-size-7 has-text-centered"></p>
<p class="has-text-grey is-size-7 has-text-centered" id="hint-text"><a href="/docs/lastfmvisualisations/1.0.0/">documentation</a> | hover over the bubble chart | click on a bubble to see associated MusicBrainz page</p>
</div>
</div>
</body>
<script src="node_modules/d3/dist/d3.min.js"></script>
<script src="bubblechart.js"></script>
<script src="lastfm.js"></script>
<script>
const API_KEY = 'c7f21b978aca554925fa95161bc803a7';
// Need to ignore errors as they are defined in external js files
// eslint-disable-next-line no-undef
const lastfmClient = new LastFmClient(API_KEY);
// eslint-disable-next-line no-undef
const chart = new BubbleChart('#bubbleChart');
// Set a click handler for the chart
chart.clickHandler = lastfmClient.createClickHandler();
// Generate a pastel colour
chart.colourFunction = () => 'hsl(' + 360 * Math.random() + ',' +
(30 + 50 * Math.random()) + '%,' + // between 30 and 80
(80 + 10 * Math.random()) + '%)'; // between 80 and 90
// eslint-disable-next-line no-unused-vars
async function buttonHandler (button, dataType) {
// Update controls with selected dataType
updateDataType(dataType);
// Get username with default set to my username if input is empty
const username = document.getElementById('username').value || 'danster103';
// Enable loading animation on the clicked button
button.classList.add('is-loading');
// Get the period and limit settings
const period = document.getElementById('periodSelect').value;
const limit = document.getElementById('limitSelect').value;
// Depending on dataType (button selected), use lastfmClient to get album or artist list
let data = null;
try {
data = await (dataType === 'albums' ? lastfmClient.fetchAlbumList(username, limit, period)
: lastfmClient.fetchArtistList(username, limit, period));
} catch (error) {
// If error, assume an empty list
data = [];
}
// Set the descriptors depending on data type (for hover over)
chart.descriptor = {
name: (dataType === 'albums' ? 'Album' : 'Artist'),
value: 'Playcount'
};
// If the data is not empty
if (data.length !== 0) {
chart.data = data;
} else {
// Else alert the user and use the json file provided as an example
alert(
'Error returned from API, using offline example account danster103. Please wait a few seconds and try again - the API can be a little intermittent. Internet required for images since they are loaded from Last.Fm API.'
);
chart.data = await d3.json('example.json');
}
// Display the bubble chart and hide the title
document.getElementById('bubbleChart').classList.remove('hidden');
document.getElementById('title').classList.add('hidden');
// Remove loading animation from clicked button
button.classList.remove('is-loading');
}
function updateDataType (dataType) {
// Reference to the switch (actually a checkbox)
const checkbox = document.getElementById('albumArtSwitch');
if (dataType === 'artists') {
// Artists do not have album art so change checked state and disable control
checkbox.checked = false;
checkbox.disabled = true;
// Propagate this change via change function
this.albumArtHandler(checkbox);
} else {
// Re-enable controls
checkbox.disabled = false;
}
}
// eslint-disable-next-line no-unused-vars
function albumArtHandler (checkbox) {
// Send the change in album art checkbox to the chart.
chart.albumArtEnabled = checkbox.checked;
}
// eslint-disable-next-line no-unused-vars
function shuffleHandler (button) {
// Call the shuffle function on the chart when button is pressed
chart.shuffle();
}
</script>