Files: a3b6e67a79ab15e9e37b77d0493be9a811e5862e / index.html
5894 bytesRaw
1 | |
2 | <html> |
3 | <head> |
4 | <meta charset=utf-8> |
5 | <title>ssb-activity</title> |
6 | <script src=common.js></script> |
7 | </head> |
8 | <body> |
9 | |
10 | <div style="text-align:center"> |
11 | <h2>Daily active users</h2> |
12 | <img id=graph src=day.png alt="Graph of daily active feeds"> |
13 | </div> |
14 | |
15 | <div id="day-info" style="float:right; text-align:right; visibility:hidden"> |
16 | <div><ins id="day"> </ins></div> |
17 | <div><ins id="day-users"> </ins> users</div> |
18 | </div> |
19 | |
20 | <div id="user-info" style="visibility:hidden"> |
21 | <div>User: <a id="user-link"><code id="user-id"> </code></a></div> |
22 | </div> |
23 | |
24 | <script> |
25 | var feedBase = 'https://viewer.scuttlebot.io/' |
26 | var ids, idColors, idsByColor = {} |
27 | var img = document.getElementById('graph') |
28 | var imgData, origImgData |
29 | var width, height |
30 | var userInfo = document.getElementById('user-info') |
31 | var userLink = document.getElementById('user-link') |
32 | var userIdText = document.getElementById('user-id').firstChild |
33 | var dayInfo = document.getElementById('day-info') |
34 | var dayText = document.getElementById('day').firstChild |
35 | var dayUsersText = document.getElementById('day-users').firstChild |
36 | |
37 | var colors = { |
38 | yellow: [255, 255, 0, 255], |
39 | red: [255, 0, 0, 255], |
40 | black: [0, 0, 0, 255], |
41 | grey: [200, 200, 200, 255], |
42 | bg: [255, 255, 255, 0], |
43 | highlight: [0, 0, 0, 255] |
44 | } |
45 | |
46 | function getJSON(url, cb) { |
47 | var req = new XMLHttpRequest() |
48 | req.open('GET', url, true) |
49 | req.onload = function () { |
50 | if (req.readyState !== 4) return |
51 | if (req.status >= 200 && req.status < 400) |
52 | cb(null, JSON.parse(req.responseText)) |
53 | else |
54 | return cb(req) |
55 | } |
56 | req.onerror = cb |
57 | req.send(null) |
58 | } |
59 | |
60 | function printError(err) { |
61 | var pre = document.createElement('pre') |
62 | document.body.appendChild(pre) |
63 | pre.appendChild(document.createTextNode(err.stack)) |
64 | } |
65 | |
66 | getJSON('ids.json', function (err, _ids) { |
67 | if (err) return printError(err) |
68 | ids = _ids |
69 | idColors = common.computeIdColors(ids) |
70 | for (var i = 0; i < ids.length; i++) { |
71 | var id = ids[i] |
72 | var color = idColors[id] |
73 | idsByColor[color] = id |
74 | } |
75 | }) |
76 | |
77 | var canvas = document.createElement('canvas') |
78 | var ctx |
79 | function onImageLoad() { |
80 | width = canvas.width = img.width |
81 | height = canvas.height = img.height |
82 | ctx = canvas.getContext('2d') |
83 | ctx.drawImage(img, 0, 0) |
84 | imgData = ctx.getImageData(0, 0, width, height) |
85 | origImgData = ctx.getImageData(0, 0, width, height) |
86 | img.parentNode.insertBefore(canvas, img) |
87 | img.parentNode.removeChild(img) |
88 | canvas.style.maxWidth = '100%' |
89 | } |
90 | if (img.complete) onImageLoad() |
91 | else img.onload = onImageLoad |
92 | |
93 | function getPoint(e) { |
94 | return [ |
95 | Math.floor(e.offsetX * width / canvas.offsetWidth), |
96 | Math.floor(e.offsetY * height / canvas.offsetHeight) |
97 | ] |
98 | } |
99 | |
100 | function colorEquals(a, b) { |
101 | return a && b |
102 | && a[3] === b[3] |
103 | && (a[3] === 0 || ( |
104 | a[0] === b[0] |
105 | && a[1] === b[1] |
106 | && a[2] === b[2] |
107 | )) |
108 | } |
109 | |
110 | function getPixel(x, y) { |
111 | var i = (y * width + x) << 2 |
112 | var data = origImgData.data |
113 | return [ |
114 | data[i], |
115 | data[i+1], |
116 | data[i+2], |
117 | data[i+3] |
118 | ] |
119 | } |
120 | |
121 | function setPixel(x, y, color) { |
122 | var i = (y * width + x) << 2 |
123 | var data = imgData.data |
124 | data[i] = color[0] |
125 | data[i+1] = color[1] |
126 | data[i+2] = color[2] |
127 | data[i+3] = 255 |
128 | } |
129 | |
130 | function replaceColor(oldColor, newColor) { |
131 | for (var x = 0; x < width; x++) { |
132 | for (var y = height-1; y > 0; y--) { |
133 | var c = getPixel(x, y) |
134 | if (colorEquals(c, colors.bg)) break |
135 | if (colorEquals(c, oldColor)) { |
136 | setPixel(x, y, newColor) |
137 | break |
138 | } |
139 | } |
140 | } |
141 | } |
142 | |
143 | function highlightColumn(x, amount) { |
144 | for (var y = 0; y < height; y++) { |
145 | var p = getPixel(x, y) |
146 | if (p[3] === 0) p[0] = p[1] = p[2] = 255 |
147 | setPixel(x, y, common.interpolate(p, colors.grey, amount)) |
148 | } |
149 | } |
150 | |
151 | function pointEquals(a, b) { |
152 | return a ? b && a[0] === b[0] && a[1] === b[1] : !b |
153 | } |
154 | |
155 | function dayStr(day) { |
156 | return isNaN(day) ? '' : |
157 | new Date(common.startTime + day * 86400000).toDateString() |
158 | } |
159 | |
160 | var usersAtInterval = {} |
161 | function countUsersAtInterval(x) { |
162 | var count = usersAtInterval[x] |
163 | if (typeof count !== 'undefined') return count |
164 | count = 0 |
165 | for (var y = height-1; y > 0; y--) { |
166 | var color = getPixel(x, y) |
167 | if (colorEquals(color, colors.bg)) break |
168 | count++ |
169 | } |
170 | return usersAtInterval[x] = count |
171 | } |
172 | |
173 | var hoverColor, hoverPoint |
174 | var clickedColor, clickedPoint |
175 | |
176 | function highlight(point) { |
177 | if (pointEquals(point, hoverPoint)) return |
178 | if (hoverPoint) highlightColumn(hoverPoint[0], 0) |
179 | if (point) highlightColumn(point[0], 0.7) |
180 | hoverPoint = point |
181 | |
182 | var color = point && getPixel(point[0], point[1]) |
183 | if (colorEquals(color, colors.bg)) { |
184 | if (hoverColor) replaceColor(hoverColor, hoverColor) |
185 | hoverColor = null |
186 | userInfo.style.visibility = 'hidden' |
187 | dayInfo.style.visibility = 'hidden' |
188 | } else if (!colorEquals(color, hoverColor)) { |
189 | if (hoverColor) replaceColor(hoverColor, hoverColor) |
190 | replaceColor(color, colors.highlight) |
191 | hoverColor = color |
192 | } |
193 | |
194 | if (!point) { |
195 | dayInfo.style.visibility = 'hidden' |
196 | } else { |
197 | dayInfo.style.visibility = 'visible' |
198 | dayText.nodeValue = dayStr(point[0]) |
199 | dayUsersText.nodeValue = countUsersAtInterval(point[0]) |
200 | } |
201 | |
202 | var id = idsByColor[color] |
203 | if (!id) { |
204 | userInfo.style.visibility = 'hidden' |
205 | } else { |
206 | userIdText.nodeValue = id |
207 | userLink.href = feedBase + encodeURIComponent(id) |
208 | userInfo.style.visibility = 'visible' |
209 | } |
210 | |
211 | ctx.putImageData(imgData, 0, 0) |
212 | } |
213 | |
214 | canvas.addEventListener('mousemove', function (e) { |
215 | highlight(getPoint(e)) |
216 | }, false) |
217 | |
218 | canvas.addEventListener('mousedown', function (e) { |
219 | var point = getPoint(e) |
220 | if (pointEquals(point, clickedPoint)) return |
221 | var color |
222 | if (point) { |
223 | color = getPixel(point[0], point[1]) |
224 | if (colorEquals(color, colors.bg)) { |
225 | color = null |
226 | point = null |
227 | } |
228 | } |
229 | clickedPoint = point |
230 | clickedColor = color |
231 | highlight(point) |
232 | }, false) |
233 | |
234 | canvas.addEventListener('mouseout', function (e) { |
235 | highlight(clickedPoint) |
236 | }, false) |
237 | |
238 | </script> |
239 | </body> |
240 | </html> |
241 |
Built with git-ssb-web