|
1 /** |
|
2 * @author raph |
|
3 */ |
|
4 |
|
5 var socket, |
|
6 tlPaper, |
|
7 twCx = { |
|
8 zoomLevel : 1, |
|
9 followLast : true, |
|
10 position : 0, |
|
11 date_levels : [ |
|
12 15 * 60 * 1000, |
|
13 5 * 60 * 1000, |
|
14 60 * 1000, |
|
15 15 * 1000 |
|
16 ], |
|
17 timeLevel : 2, |
|
18 deltaX : 30, |
|
19 tlWidth : 98, |
|
20 tlHeight : 450, |
|
21 globalWords : {} |
|
22 }, |
|
23 tlBuffer = '', |
|
24 relHover = null, |
|
25 wheelDelta = 0, |
|
26 rx_word = /[^ \.&;,'"!\?@#\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g; |
|
27 |
|
28 function arc(source, target) { |
|
29 var x3 = .3 * target.y - .3 * source.y + .8 * source.x + .2 * target.x; |
|
30 var y3 = .8 * source.y + .2 * target.y - .3 * target.x + .3 * source.x; |
|
31 var x4 = .3 * target.y - .3 * source.y + .2 * source.x + .8 * target.x; |
|
32 var y4 = .2 * source.y + .8 * target.y - .3 * target.x + .3 * source.x; |
|
33 return "M" + source.x + " " + source.y + "C" + [x3, y3, x4, y4, target.x, target.y].join(" "); |
|
34 } |
|
35 |
|
36 function countWords(text, wordobj) { |
|
37 var tab = text.match(rx_word); |
|
38 for (var i in tab) { |
|
39 var word = tab[i].toLowerCase(); |
|
40 if (wordobj[word]) { |
|
41 wordobj[word]++; |
|
42 } else { |
|
43 wordobj[word] = 1; |
|
44 } |
|
45 } |
|
46 } |
|
47 |
|
48 function addTweet(tweet) { |
|
49 function backRef(source_id, target_id, type) { |
|
50 var target = tweetById(target_id); |
|
51 if (target) { |
|
52 var brobj = { |
|
53 "referenced_by_id" : source_id, |
|
54 "type" : type |
|
55 } |
|
56 if (target.backRefs) { |
|
57 target.backRefs.push(brobj); |
|
58 } else { |
|
59 target.backRefs = [ brobj ] |
|
60 } |
|
61 } |
|
62 } |
|
63 |
|
64 var tab = tweet.text.split(/\&\#|\;/), |
|
65 txt = ''; |
|
66 for (i = 0; i < tab.length; i++) { |
|
67 txt += (i % 2 && parseInt(tab[i]) != NaN) ? String.fromCharCode(tab[i]) : tab[i]; |
|
68 } |
|
69 tweet.text = txt; |
|
70 |
|
71 twCx.tweets.push(tweet); |
|
72 twCx.idIndex.push(tweet.id); |
|
73 |
|
74 if (tweet.in_reply_to_status_id) { |
|
75 backRef( tweet.id, tweet.in_reply_to_status_id, "reply" ); |
|
76 } |
|
77 if (tweet.retweeted_status) { |
|
78 backRef( tweet.id, tweet.retweeted_status.id, "retweet" ); |
|
79 } |
|
80 |
|
81 countWords(tweet.text, twCx.globalWords); |
|
82 |
|
83 var creadate = new Date(tweet.created_at).valueOf(); |
|
84 if (!twCx.timeline.length) { |
|
85 twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(creadate / twCx.date_levels[0])) ] |
|
86 } |
|
87 while (creadate > twCx.timeline[twCx.timeline.length - 1].end) { |
|
88 twCx.timeline.push( populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end) ); |
|
89 } |
|
90 insertIntoDateStruct(twCx.timeline, tweet); |
|
91 } |
|
92 |
|
93 function getSliceContent(slice) { |
|
94 if (slice.slices) { |
|
95 var result = []; |
|
96 for (var i in slice.slices) { |
|
97 result = result.concat(getSliceContent(slice.slices[i])); |
|
98 } |
|
99 } else { |
|
100 var result = slice.tweets; |
|
101 } |
|
102 return result; |
|
103 } |
|
104 |
|
105 function flattenDateStruct(slices, target_level) { |
|
106 var current_level = slices[0].level, |
|
107 result = []; |
|
108 if (current_level < target_level) { |
|
109 if (slices[0].slices) { |
|
110 for (var i in slices) { |
|
111 result = result.concat(flattenDateStruct(slices[i].slices, target_level)); |
|
112 } |
|
113 } |
|
114 } |
|
115 else { |
|
116 for (var i in slices) { |
|
117 result.push({ |
|
118 "start" : slices[i].start, |
|
119 "end" : slices[i].end, |
|
120 "tweets" : getSliceContent(slices[i]) |
|
121 }); |
|
122 } |
|
123 } |
|
124 return result; |
|
125 } |
|
126 |
|
127 function trimFDS(slices) { |
|
128 while (slices[0].tweets.length == 0) { |
|
129 slices.splice(0,1); |
|
130 } |
|
131 while (slices[slices.length - 1].tweets.length == 0) { |
|
132 slices.pop(); |
|
133 } |
|
134 return slices; |
|
135 } |
|
136 |
|
137 function populateDateStruct(level, start) { |
|
138 var end = start + twCx.date_levels[level], |
|
139 struct = { |
|
140 "level" : level, |
|
141 "start" : start, |
|
142 "end" : end |
|
143 }; |
|
144 if (level < twCx.date_levels.length - 1) { |
|
145 struct.slices = []; |
|
146 var newstart = start; |
|
147 while (newstart < end) { |
|
148 struct.slices.push(populateDateStruct(level + 1, newstart)); |
|
149 newstart += twCx.date_levels[level + 1]; |
|
150 } |
|
151 } else { |
|
152 struct.tweets = []; |
|
153 } |
|
154 return struct; |
|
155 } |
|
156 |
|
157 function insertIntoDateStruct(slices, tweet) { |
|
158 var creadate = new Date(tweet.created_at).valueOf(); |
|
159 for (var i in slices) { |
|
160 if (creadate < slices[i].end) { |
|
161 if (slices[i].slices) { |
|
162 insertIntoDateStruct(slices[i].slices, tweet); |
|
163 } else { |
|
164 slices[i].tweets.push(tweet.id); |
|
165 } |
|
166 break; |
|
167 } |
|
168 } |
|
169 } |
|
170 |
|
171 function placeHolder(className) { |
|
172 return '<li class="placeholder ' + className + '"></li>'; |
|
173 } |
|
174 |
|
175 function tweetById(tweetid) { |
|
176 var pos = twCx.idIndex.indexOf(tweetid); |
|
177 return (pos == -1) ? false : twCx.tweets[pos]; |
|
178 } |
|
179 |
|
180 function selectTweet(tweetid) { |
|
181 var pos = twCx.idIndex.indexOf(tweetid); |
|
182 if (pos != -1) { |
|
183 twCx.position = pos; |
|
184 twCx.followLast = (twCx.position == twCx.tweets.length - 1); |
|
185 updateDisplay() |
|
186 } |
|
187 } |
|
188 |
|
189 function tweetToHtml(tweet, className, elName) { |
|
190 if (!tweet) { |
|
191 return placeHolder(className); |
|
192 } |
|
193 var el = (elName ? elName : 'li'); |
|
194 var html = '<' + el + ' class="tweet ' + className + '" id="tweet_' + tweet.id + '"'; |
|
195 if (className != 'full') { |
|
196 html += ' onclick="selectTweet(\'' + tweet.id + '\'); return false;" onmouseover="rolloverTweet(\'' + tweet.id + '\');"'; |
|
197 } else { |
|
198 html += ' onmouseover="$(\'#hovertweet\').hide();"'; |
|
199 } |
|
200 if (twCx.followLast && className == 'full' && el == 'li') { |
|
201 html += ' style="display: none"'; |
|
202 } |
|
203 html += '>'; |
|
204 if (tweet.annotations.length) { |
|
205 html += '<div class="annotations">'; |
|
206 for (var i in tweet.annotations) { |
|
207 html += '<div class="annotation" style="width:' + (100/tweet.annotations.length) + '%; background:' + annotations[tweet.annotations[i]].colors[(className == 'icons' ? 'timeline' : 'tweet')] + '"></div>'; |
|
208 } |
|
209 html += '</div>'; |
|
210 } |
|
211 html += '<div class="twmain">'; |
|
212 a_user = '<a href="http://twitter.com/' + tweet.user.screen_name + '" var target="_blank" title="' + tweet.user.name + '">'; |
|
213 html += '<div class="around_img">' + a_user + '<img class="profile_image" src="' + tweet.user.profile_image_url + '" /></a>'; |
|
214 if (className == 'full') { |
|
215 html += '<p class="created_at">' + new Date(tweet.created_at).toLocaleTimeString() + '</p>'; |
|
216 } |
|
217 html += '</div>'; |
|
218 if (className != 'icons') { |
|
219 lastend = 0; |
|
220 var txt = '', |
|
221 entities = []; |
|
222 for (var i in tweet.entities.hashtags) { |
|
223 entities.push({ |
|
224 "start" : tweet.entities.hashtags[i].indices[0], |
|
225 "end" : tweet.entities.hashtags[i].indices[1], |
|
226 "html" : '<a href="http://twitter.com/search?q=%23' + tweet.entities.hashtags[i].text + '" target="_blank">#' + tweet.entities.hashtags[i].text + '</a>' |
|
227 }); |
|
228 } |
|
229 for (var i in tweet.entities.urls) { |
|
230 entities.push({ |
|
231 "start" : tweet.entities.urls[i].indices[0], |
|
232 "end" : tweet.entities.urls[i].indices[1], |
|
233 "html" : '<a href="' + tweet.entities.urls[i].expanded_url + '" target="_blank">' + tweet.entities.urls[i].expanded_url + '</a>' |
|
234 }); |
|
235 } |
|
236 for (var i in tweet.entities.user_mentions) { |
|
237 entities.push({ |
|
238 "start" : tweet.entities.user_mentions[i].indices[0], |
|
239 "end" : tweet.entities.user_mentions[i].indices[1], |
|
240 "html" : '<a href="http://twitter.com/' + tweet.entities.user_mentions[i].screen_name + '" target="_blank" title="' + tweet.entities.user_mentions[i].name + '">@' + tweet.entities.user_mentions[i].screen_name + '</a>' |
|
241 }); |
|
242 } |
|
243 entities.sort(function(a, b) { return a.start - b.start }); |
|
244 for (var i in entities) { |
|
245 txt += tweet.text.substring(lastend, entities[i].start) + entities[i].html; |
|
246 lastend = entities[i].end; |
|
247 } |
|
248 txt += tweet.text.substring(lastend); |
|
249 html += '<p class="tweet_text"><b>' + a_user + '@' + tweet.user.screen_name + '</b></a>: ' + txt + '</p>'; |
|
250 } |
|
251 html += '</div></' + el + '>'; |
|
252 return html; |
|
253 } |
|
254 |
|
255 function tlIdFromPos(x, y) { |
|
256 if (x < twCx.deltaX) { |
|
257 return null; |
|
258 } |
|
259 var ligne = Math.floor(( twCx.tlHeight - y ) / twCx.scaleY), |
|
260 colonne = Math.floor(( x - twCx.deltaX ) / twCx.scaleX ), |
|
261 l = 0; |
|
262 if (colonne >= twCx.tlOnDisplay[ligne].totalTweets) { |
|
263 return null; |
|
264 } |
|
265 for (var i in twCx.tlOnDisplay[ligne].displayData) { |
|
266 var nl = l + twCx.tlOnDisplay[ligne].displayData[i].length; |
|
267 if (colonne < nl) { |
|
268 return { |
|
269 "id" : twCx.tlOnDisplay[ligne].displayData[i][colonne - l], |
|
270 "annotation" : i |
|
271 } |
|
272 } |
|
273 l = nl; |
|
274 } |
|
275 } |
|
276 |
|
277 function tlPosTweet(tweet, annotation) { |
|
278 var x, |
|
279 y, |
|
280 dt = new Date(tweet.created_at).valueOf(), |
|
281 ann = ( annotation ? annotation : ( tweet.annotations.length ? tweet.annotations[0] : 'default' ) ); |
|
282 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
|
283 if (twCx.tlOnDisplay[i].end > dt) { |
|
284 y = twCx.tlHeight - (i + .5) * twCx.scaleY; |
|
285 var l = 0; |
|
286 for (var j in twCx.tlOnDisplay[i].displayData) { |
|
287 if (j == ann) { |
|
288 var p = twCx.tlOnDisplay[i].displayData[j].indexOf(tweet.id); |
|
289 if (p != -1) { |
|
290 x = twCx.deltaX + twCx.scaleX * ( p + l + .5 ); |
|
291 } |
|
292 break; |
|
293 } |
|
294 l += twCx.tlOnDisplay[i].displayData[j].length; |
|
295 } |
|
296 break; |
|
297 } |
|
298 } |
|
299 return ( x && y ? { "x" : x, "y" : y } : null); |
|
300 } |
|
301 |
|
302 function rolloverTweet(tweetid, annotation) { |
|
303 var t = tweetById(tweetid), |
|
304 p = tlPosTweet(t, annotation); |
|
305 if (t && p) { |
|
306 var ptl = $("#timeline").offset(); |
|
307 $("#hovercontent").html(tweetToHtml(t, 'full', 'div')); |
|
308 $("#hovertweet").css({ |
|
309 "left" : parseInt(ptl.left + p.x) + "px", |
|
310 "top" : parseInt(ptl.top + p.y), |
|
311 "display" : "block"}); |
|
312 if (relHover) { |
|
313 relHover.remove(); |
|
314 } |
|
315 relHover = drawTweetPos(p, '#ffffff') |
|
316 } |
|
317 } |
|
318 |
|
319 function drawTweetPos(pos, color) { |
|
320 var rel = tlPaper.rect(pos.x - .5 * twCx.scaleX, pos.y - .5 * twCx.scaleY, twCx.scaleX, twCx.scaleY); |
|
321 rel.attr({ "stroke" : color }); |
|
322 return rel; |
|
323 } |
|
324 |
|
325 function updateDisplay() { |
|
326 var p = twCx.position, |
|
327 l = twCx.tweets.length, |
|
328 lines = 0, |
|
329 html = '', |
|
330 tweetsOnDisplay = [], |
|
331 localWords = {}; |
|
332 |
|
333 function pushTweet(tp, className) { |
|
334 if (tp < l && tp >= 0) { |
|
335 html += tweetToHtml(twCx.tweets[tp], className) |
|
336 tweetsOnDisplay.push(tp); |
|
337 countWords(twCx.tweets[tp].text, localWords); |
|
338 } else { |
|
339 html += placeHolder(className); |
|
340 } |
|
341 } |
|
342 |
|
343 if (l > p + 18) { |
|
344 lines++; |
|
345 for (var i = p + 31; i >= p + 18; i--) { |
|
346 pushTweet(i, 'icons'); |
|
347 } |
|
348 } |
|
349 if (l > p + 4) { |
|
350 lines++; |
|
351 for (var i = p + 17; i >= p + 4; i--) { |
|
352 pushTweet(i, 'icons'); |
|
353 } |
|
354 } |
|
355 for (var k = 3; k >= 1; k--) { |
|
356 if (l > p + k) { |
|
357 lines++; |
|
358 pushTweet(p + k, 'half'); |
|
359 } |
|
360 } |
|
361 pushTweet(p, 'full'); |
|
362 var n = p - 1; |
|
363 for (var i = 0; i < Math.min(6, Math.max(3, 6 - lines)); i++) { |
|
364 if (n < 0) { |
|
365 break; |
|
366 } |
|
367 pushTweet(n, 'half'); |
|
368 n--; |
|
369 } |
|
370 for (var i = 0; i < 14 * Math.min(4, Math.max(2, 7 - lines)); i++) { |
|
371 if (n < 0) { |
|
372 break; |
|
373 } |
|
374 pushTweet(n, 'icons'); |
|
375 n--; |
|
376 } |
|
377 if (html != tlBuffer) { |
|
378 $("#tweetlist").html(html); |
|
379 $(".tweet.full").fadeIn(); |
|
380 tlBuffer = html; |
|
381 } |
|
382 |
|
383 for (var j in localWords) { |
|
384 if (localWords[j] < 2) delete localWords[j]; |
|
385 } |
|
386 var tab = []; |
|
387 for (var j in localWords) { |
|
388 tab.push({ |
|
389 "word": j, |
|
390 "freq" : localWords[j] / Math.log(twCx.globalWords[j]) |
|
391 }); |
|
392 } |
|
393 tab.sort( function(a,b){ return b.freq - a.freq }).splice(10); |
|
394 $("#motscles").html(tab.map(function(t) { return t.word }).join(", ")) |
|
395 |
|
396 twCx.tlOnDisplay = trimFDS(flattenDateStruct(twCx.timeline, twCx.timeLevel)); |
|
397 twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length; |
|
398 var maxTweets = 0, |
|
399 startTl = 0, |
|
400 endTl = 0, |
|
401 startTw = new Date(twCx.tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].created_at).valueOf(), |
|
402 endTw = new Date(twCx.tweets[tweetsOnDisplay[0]].created_at).valueOf(); |
|
403 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
|
404 if (startTw >= twCx.tlOnDisplay[i].start && startTw < twCx.tlOnDisplay[i].end) { |
|
405 startTl = i; |
|
406 } |
|
407 if (endTw >= twCx.tlOnDisplay[i].start && endTw < twCx.tlOnDisplay[i].end) { |
|
408 endTl = i; |
|
409 } |
|
410 var displayData = {}; |
|
411 for (var j in annotations) { |
|
412 displayData[j] = []; |
|
413 } |
|
414 for (var j in twCx.tlOnDisplay[i].tweets) { |
|
415 var tweetid = twCx.tlOnDisplay[i].tweets[j], |
|
416 tweet = tweetById(tweetid); |
|
417 if (tweet) { |
|
418 if (tweet.annotations.length) { |
|
419 for (var k in tweet.annotations) { |
|
420 displayData[tweet.annotations[k]].push(tweetid); |
|
421 } |
|
422 } else { |
|
423 displayData['default'].push(tweetid); |
|
424 } |
|
425 } |
|
426 } |
|
427 var nbT = 0; |
|
428 for (var j in displayData) { |
|
429 nbT += displayData[j].length; |
|
430 } |
|
431 maxTweets = Math.max(maxTweets, nbT); |
|
432 twCx.tlOnDisplay[i].displayData = displayData; |
|
433 twCx.tlOnDisplay[i].totalTweets = nbT; |
|
434 } |
|
435 twCx.scaleX = ( twCx.tlWidth - twCx.deltaX ) / maxTweets; |
|
436 tlPaper.clear(); |
|
437 relHover = null; |
|
438 |
|
439 // dessin de la date de début |
|
440 |
|
441 tlPaper.text(2, twCx.tlHeight - 7, new Date(twCx.tlOnDisplay[0].start).toTimeString().substr(0,5)) |
|
442 .attr({ "text-anchor" : "start", "font-size": "9px" }); |
|
443 |
|
444 // dessin de la date de fin |
|
445 |
|
446 tlPaper.text(2, 7, new Date(twCx.tlOnDisplay[twCx.tlOnDisplay.length - 1].end).toTimeString().substr(0,5)) |
|
447 .attr({ "text-anchor" : "start", "font-size": "9px" }); |
|
448 |
|
449 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
|
450 var n = 0, |
|
451 posY = twCx.tlHeight - ( i + 1 ) * twCx.scaleY; |
|
452 for (var j in twCx.tlOnDisplay[i].displayData) { |
|
453 var l = twCx.tlOnDisplay[i].displayData[j].length; |
|
454 if (l > 0) { |
|
455 tlPaper.rect( twCx.deltaX + n * twCx.scaleX, posY, l * twCx.scaleX, twCx.scaleY ) |
|
456 .attr({"stroke" : "none", "fill" : annotations[j].colors.timeline }); |
|
457 n += l; |
|
458 } |
|
459 } |
|
460 |
|
461 // Si on est à une demi-heure, on trace un axe secondaire + heure |
|
462 |
|
463 if (i < twCx.tlOnDisplay.length - 1 && !(new Date(twCx.tlOnDisplay[i].end).valueOf() % 1800000)) { |
|
464 tlPaper.path("M0 "+posY+"L" + twCx.tlWidth +" "+posY).attr({"stroke":"#ccc"}); |
|
465 tlPaper.text(2, posY, new Date(twCx.tlOnDisplay[i].end).toTimeString().substr(0,5)).attr({ "text-anchor" : "start", "font-size": "9px" }); |
|
466 } |
|
467 } |
|
468 |
|
469 // Dessin de la correspondance liste-timeline |
|
470 |
|
471 var startY = twCx.tlHeight - startTl * twCx.scaleY, |
|
472 endY = twCx.tlHeight - ( endTl + 1 ) * twCx.scaleY; |
|
473 tlPaper.path("M0 " + twCx.tlHeight + "L" + twCx.deltaX + " " + startY + "L" + twCx.tlWidth + " " + startY + "L" + twCx.tlWidth + " " + endY + "L" + twCx.deltaX + " " + endY + "L0 0" ) |
|
474 .attr({ "stroke" : "none", "fill" : "#000080", "opacity" : .1 }); |
|
475 |
|
476 // dessin du tweet courant |
|
477 |
|
478 var posp = tlPosTweet(twCx.tweets[p]); |
|
479 if (posp) { |
|
480 |
|
481 drawTweetPos(posp, "#ffff00"); |
|
482 |
|
483 // dessin des liens entre tweets |
|
484 |
|
485 function tweetAndArc(a, b, aorb) { |
|
486 if (a && b) { |
|
487 drawTweetPos(aorb ? a : b, "#00c000"); |
|
488 tlPaper.path(arc(a,b)) |
|
489 .attr({ "stroke" : "#e000e0", "stroke-width" : 1.5 }); |
|
490 } |
|
491 } |
|
492 |
|
493 if (twCx.tweets[p].retweeted_status) { |
|
494 var t = tweetById(twCx.tweets[p].retweeted_status.id); |
|
495 if (t) { |
|
496 tweetAndArc(posp, tlPosTweet(t)); |
|
497 } |
|
498 } |
|
499 |
|
500 if (twCx.tweets[p].in_reply_to_status_id) { |
|
501 var t = tweetById(twCx.tweets[p].in_reply_to_status_id); |
|
502 if (t) { |
|
503 tweetAndArc(posp, tlPosTweet(t)); |
|
504 } |
|
505 } |
|
506 |
|
507 if (twCx.tweets[p].backRefs) { |
|
508 for (var i in twCx.tweets[p].backRefs) { |
|
509 var t = tweetById(twCx.tweets[p].backRefs[i].referenced_by_id); |
|
510 if (t) { |
|
511 tweetAndArc(tlPosTweet(t), posp, true); |
|
512 } |
|
513 } |
|
514 } |
|
515 |
|
516 } |
|
517 |
|
518 } |
|
519 |
|
520 $(document).ready(function() { |
|
521 tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight); |
|
522 socket = io.connect('http://' + document.location.hostname ); |
|
523 socket.on("initial_data", function(data) { |
|
524 twCx.timeline = []; |
|
525 twCx.idIndex = []; |
|
526 twCx.tweets = []; |
|
527 for (var i in data.tweets) { |
|
528 addTweet(data.tweets[i]); |
|
529 } |
|
530 if (twCx.followLast) { |
|
531 twCx.position = twCx.tweets.length - 1; |
|
532 } |
|
533 updateDisplay(); |
|
534 }); |
|
535 socket.on("update", function(data) { |
|
536 for (var i in data.new_tweets) { |
|
537 addTweet(data.new_tweets[i]); |
|
538 } |
|
539 if (twCx.followLast) { |
|
540 twCx.position = twCx.tweets.length - 1; |
|
541 } |
|
542 updateDisplay(); |
|
543 }); |
|
544 |
|
545 |
|
546 $("#tweetlist").mousewheel(function(e, d) { |
|
547 wheelDelta += d; |
|
548 if (Math.abs(wheelDelta) >= 1) { |
|
549 twCx.position = Math.min( twCx.tweets.length - 1, Math.max(0, parseInt(wheelDelta) + twCx.position ) ); |
|
550 twCx.followLast = (twCx.position == twCx.tweets.length - 1); |
|
551 updateDisplay(); |
|
552 wheelDelta = 0; |
|
553 } |
|
554 return false; |
|
555 }); |
|
556 $("#timeline").mousewheel(function(e, d) { |
|
557 wheelDelta += d; |
|
558 if (Math.abs(wheelDelta) >= 1) { |
|
559 if (wheelDelta > 0) { |
|
560 tl = Math.min(twCx.date_levels.length - 1, twCx.timeLevel + 1); |
|
561 } else { |
|
562 tl = Math.max(0, twCx.timeLevel - 1); |
|
563 } |
|
564 if (tl != twCx.timeLevel) { |
|
565 twCx.timeLevel = tl; |
|
566 updateDisplay(); |
|
567 } |
|
568 wheelDelta = 0; |
|
569 } |
|
570 return false; |
|
571 }); |
|
572 $("#timeline, #tweetlist").mouseout(function() { |
|
573 $("#hovertweet").hide(); |
|
574 }); |
|
575 $("#timeline").mousemove(function(evt) { |
|
576 var twid = tlIdFromPos(evt.offsetX, evt.offsetY); |
|
577 if (twid) { |
|
578 rolloverTweet(twid.id, twid.annotation); |
|
579 } else { |
|
580 $("#hovertweet").hide(); |
|
581 } |
|
582 }); |
|
583 $("#timeline").click(function(evt) { |
|
584 var twid = tlIdFromPos(evt.offsetX, evt.offsetY); |
|
585 if (twid) { |
|
586 selectTweet(twid.id); |
|
587 } |
|
588 }); |
|
589 }); |