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