16 ], |
16 ], |
17 timeLevel : 2, |
17 timeLevel : 2, |
18 deltaX : 30, |
18 deltaX : 30, |
19 tlWidth : 98, |
19 tlWidth : 98, |
20 tlHeight : 450, |
20 tlHeight : 450, |
21 globalWords : {} |
21 globalWords : {}, |
|
22 refMouse : { x : 0, y : 0}, |
|
23 refPosTl : { x : 0, y : 0}, |
|
24 tlMouseMoved : false, |
|
25 tlMouseClicked : false |
22 }, |
26 }, |
23 tlBuffer = '', |
27 tlBuffer = '', |
24 relHover = null, |
28 relHover = null, |
25 wheelDelta = 0, |
29 wheelDelta = 0, |
26 rx_word = /[^ \.&;,'"!\?@#\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g; |
30 rx_word = /[^ \.&;,'"!\?@#\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g, |
|
31 stop_list = [ 'and', 'les', 'the' ]; |
27 |
32 |
28 function arc(source, target) { |
33 function arc(source, target) { |
29 var x3 = .3 * target.y - .3 * source.y + .8 * source.x + .2 * target.x; |
34 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; |
35 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; |
36 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; |
37 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(" "); |
38 return "M" + source.x + " " + source.y + "C" + [x3, y3, x4, y4, target.x, target.y].join(" "); |
34 } |
39 } |
35 |
40 |
36 function countWords(text, wordobj) { |
41 function countWords(text, wordobj) { |
37 var tab = text.match(rx_word); |
42 var tab = text.replace(/https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,'').match(rx_word); |
38 for (var i in tab) { |
43 for (var i in tab) { |
39 var word = tab[i].toLowerCase(); |
44 var word = tab[i].toLowerCase(); |
40 if (wordobj[word]) { |
45 if (stop_list.indexOf(word) == -1 && tracking_keywords.indexOf(word) == -1) { |
41 wordobj[word]++; |
46 if (wordobj[word]) { |
42 } else { |
47 wordobj[word]++; |
43 wordobj[word] = 1; |
48 } else { |
|
49 wordobj[word] = 1; |
|
50 } |
44 } |
51 } |
45 } |
52 } |
46 } |
53 } |
47 |
54 |
48 function addTweet(tweet) { |
55 function addTweet(tweet) { |
59 target.backRefs = [ brobj ] |
66 target.backRefs = [ brobj ] |
60 } |
67 } |
61 } |
68 } |
62 } |
69 } |
63 |
70 |
64 var tab = tweet.text.split(/\&\#|\;/), |
71 var ann = []; |
65 txt = ''; |
72 for (var j in annotations) { |
66 for (i = 0; i < tab.length; i++) { |
73 if (j != "default") { |
67 txt += (i % 2 && parseInt(tab[i]) != NaN) ? String.fromCharCode(tab[i]) : tab[i]; |
74 for (var k in annotations[j].keywords) { |
68 } |
75 if (tweet.text.search(annotations[j].keywords[k]) != -1) { |
69 tweet.text = txt; |
76 ann.push(j); |
|
77 break; |
|
78 } |
|
79 } |
|
80 } |
|
81 } |
|
82 tweet.annotations = ann; |
|
83 |
|
84 var tab = tweet.text.match(/\&\#[0-9]+\;/g); |
|
85 for (var i in tab) { |
|
86 var n = parseInt(tab[i].substr(2)); |
|
87 if (n != NaN) { |
|
88 tweet.text = tweet.text.replace(tab[i], String.fromCharCode(n )); |
|
89 } |
|
90 } |
70 |
91 |
71 twCx.tweets.push(tweet); |
92 twCx.tweets.push(tweet); |
72 twCx.idIndex.push(tweet.id); |
93 twCx.idIndex.push(tweet.id); |
73 |
94 |
74 if (tweet.in_reply_to_status_id) { |
95 if (tweet.in_reply_to_status_id) { |
78 backRef( tweet.id, tweet.retweeted_status.id, "retweet" ); |
99 backRef( tweet.id, tweet.retweeted_status.id, "retweet" ); |
79 } |
100 } |
80 |
101 |
81 countWords(tweet.text, twCx.globalWords); |
102 countWords(tweet.text, twCx.globalWords); |
82 |
103 |
83 var creadate = new Date(tweet.created_at).valueOf(); |
104 var creadate = tweet.date_value; |
84 if (!twCx.timeline.length) { |
105 if (!twCx.timeline.length) { |
85 twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(creadate / twCx.date_levels[0])) ] |
106 twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(creadate / twCx.date_levels[0])) ] |
86 } |
107 } |
87 while (creadate > twCx.timeline[twCx.timeline.length - 1].end) { |
108 while (creadate > twCx.timeline[twCx.timeline.length - 1].end) { |
88 twCx.timeline.push( populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end) ); |
109 twCx.timeline.push( populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end) ); |
153 } |
174 } |
154 return struct; |
175 return struct; |
155 } |
176 } |
156 |
177 |
157 function insertIntoDateStruct(slices, tweet) { |
178 function insertIntoDateStruct(slices, tweet) { |
158 var creadate = new Date(tweet.created_at).valueOf(); |
179 var creadate = tweet.date_value; |
159 for (var i in slices) { |
180 for (var i in slices) { |
160 if (creadate < slices[i].end) { |
181 if (creadate < slices[i].end) { |
161 if (slices[i].slices) { |
182 if (slices[i].slices) { |
162 insertIntoDateStruct(slices[i].slices, tweet); |
183 insertIntoDateStruct(slices[i].slices, tweet); |
163 } else { |
184 } else { |
210 } |
231 } |
211 html += '<div class="twmain">'; |
232 html += '<div class="twmain">'; |
212 a_user = '<a href="http://twitter.com/' + tweet.user.screen_name + '" var target="_blank" title="' + tweet.user.name + '">'; |
233 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>'; |
234 html += '<div class="around_img">' + a_user + '<img class="profile_image" src="' + tweet.user.profile_image_url + '" /></a>'; |
214 if (className == 'full') { |
235 if (className == 'full') { |
215 html += '<p class="created_at">' + new Date(tweet.created_at).toLocaleTimeString() + '</p>'; |
236 html += '<p class="created_at">' + new Date(tweet.date_value).toLocaleTimeString() + '</p>'; |
216 } |
237 } |
217 html += '</div>'; |
238 html += '</div>'; |
218 if (className != 'icons') { |
239 if (className != 'icons') { |
219 lastend = 0; |
240 lastend = 0; |
220 var txt = '', |
241 var txt = '', |
250 } |
271 } |
251 html += '</div></' + el + '>'; |
272 html += '</div></' + el + '>'; |
252 return html; |
273 return html; |
253 } |
274 } |
254 |
275 |
255 function tlIdFromPos(x, y) { |
276 function tlIdFromPos(x, y, outside) { |
256 if (x < twCx.deltaX) { |
277 var ligne = Math.min( twCx.tlOnDisplay.length - 1, Math.max( 0, Math.floor(( twCx.tlHeight - y ) / twCx.scaleY) ) ), |
257 return null; |
|
258 } |
|
259 var ligne = Math.floor(( twCx.tlHeight - y ) / twCx.scaleY), |
|
260 colonne = Math.floor(( x - twCx.deltaX ) / twCx.scaleX ), |
278 colonne = Math.floor(( x - twCx.deltaX ) / twCx.scaleX ), |
261 l = 0; |
279 l = 0; |
262 if (colonne >= twCx.tlOnDisplay[ligne].totalTweets) { |
280 if (colonne >= twCx.tlOnDisplay[ligne].totalTweets || colonne < 0 ) { |
263 return null; |
281 if (outside) { |
|
282 colonne = Math.min( twCx.tlOnDisplay[ligne].totalTweets - 1, Math.max( 0, colonne )); |
|
283 } else { |
|
284 return null; |
|
285 } |
264 } |
286 } |
265 for (var i in twCx.tlOnDisplay[ligne].displayData) { |
287 for (var i in twCx.tlOnDisplay[ligne].displayData) { |
266 var nl = l + twCx.tlOnDisplay[ligne].displayData[i].length; |
288 var nl = l + twCx.tlOnDisplay[ligne].displayData[i].length; |
267 if (colonne < nl) { |
289 if (colonne < nl) { |
268 return { |
290 return { |
275 } |
297 } |
276 |
298 |
277 function tlPosTweet(tweet, annotation) { |
299 function tlPosTweet(tweet, annotation) { |
278 var x, |
300 var x, |
279 y, |
301 y, |
280 dt = new Date(tweet.created_at).valueOf(), |
302 dt = tweet.date_value, |
281 ann = ( annotation ? annotation : ( tweet.annotations.length ? tweet.annotations[0] : 'default' ) ); |
303 ann = ( annotation ? annotation : ( tweet.annotations.length ? tweet.annotations[0] : 'default' ) ); |
282 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
304 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
283 if (twCx.tlOnDisplay[i].end > dt) { |
305 if (twCx.tlOnDisplay[i].end > dt) { |
284 y = twCx.tlHeight - (i + .5) * twCx.scaleY; |
306 y = twCx.tlHeight - (i + .5) * twCx.scaleY; |
285 var l = 0; |
307 var l = 0; |
316 } |
338 } |
317 } |
339 } |
318 |
340 |
319 function drawTweetPos(pos, color) { |
341 function drawTweetPos(pos, color) { |
320 var rel = tlPaper.rect(pos.x - .5 * twCx.scaleX, pos.y - .5 * twCx.scaleY, twCx.scaleX, twCx.scaleY); |
342 var rel = tlPaper.rect(pos.x - .5 * twCx.scaleX, pos.y - .5 * twCx.scaleY, twCx.scaleX, twCx.scaleY); |
321 rel.attr({ "stroke" : color }); |
343 rel.attr({ "stroke" : color, "fill" : color, "fill-opacity" : .1 }); |
322 return rel; |
344 return rel; |
323 } |
345 } |
324 |
346 |
325 function updateDisplay() { |
347 function updateDisplay() { |
326 var p = twCx.position, |
348 var p = twCx.position, |
327 l = twCx.tweets.length, |
349 l = twCx.tweets.length, |
328 lines = 0, |
350 lines = 0, |
|
351 ppy = 0, |
329 html = '', |
352 html = '', |
330 tweetsOnDisplay = [], |
353 tweetsOnDisplay = [], |
331 localWords = {}; |
354 localWords = {}; |
332 |
355 |
333 function pushTweet(tp, className) { |
356 function pushTweet(tp, className) { |
340 } |
363 } |
341 } |
364 } |
342 |
365 |
343 if (l > p + 18) { |
366 if (l > p + 18) { |
344 lines++; |
367 lines++; |
|
368 ppy += 20; |
345 for (var i = p + 31; i >= p + 18; i--) { |
369 for (var i = p + 31; i >= p + 18; i--) { |
346 pushTweet(i, 'icons'); |
370 pushTweet(i, 'icons'); |
347 } |
371 } |
348 } |
372 } |
349 if (l > p + 4) { |
373 if (l > p + 4) { |
350 lines++; |
374 lines++; |
|
375 ppy += 20; |
351 for (var i = p + 17; i >= p + 4; i--) { |
376 for (var i = p + 17; i >= p + 4; i--) { |
352 pushTweet(i, 'icons'); |
377 pushTweet(i, 'icons'); |
353 } |
378 } |
354 } |
379 } |
355 for (var k = 3; k >= 1; k--) { |
380 for (var k = 3; k >= 1; k--) { |
356 if (l > p + k) { |
381 if (l > p + k) { |
|
382 ppy += 47; |
357 lines++; |
383 lines++; |
358 pushTweet(p + k, 'half'); |
384 pushTweet(p + k, 'half'); |
359 } |
385 } |
360 } |
386 } |
361 pushTweet(p, 'full'); |
387 pushTweet(p, 'full'); |
385 } |
411 } |
386 var tab = []; |
412 var tab = []; |
387 for (var j in localWords) { |
413 for (var j in localWords) { |
388 tab.push({ |
414 tab.push({ |
389 "word": j, |
415 "word": j, |
390 "freq" : localWords[j] / Math.log(twCx.globalWords[j]) |
416 "freq" : localWords[j] / Math.log(1+twCx.globalWords[j]) |
391 }); |
417 }); |
392 } |
418 } |
393 tab.sort( function(a,b){ return b.freq - a.freq }).splice(10); |
419 tab.sort( function(a,b){ return ( b.freq - a.freq ) }).splice(15); |
394 $("#motscles").html(tab.map(function(t) { return t.word }).join(", ")) |
420 var minfreq = tab[tab.length - 1].freq, |
|
421 maxfreq = Math.max(minfreq + .1, tab[0].freq), |
|
422 echfreq = 8 / Math.sqrt( maxfreq - minfreq ); |
|
423 $("#motscles").html(tab.map(function(t) { return '<span style="font-size: ' + Math.floor( ( 12 + Math.sqrt( t.freq - minfreq ) * echfreq ) ) + 'px">' + t.word + '</span>' }).join(" ")) |
395 |
424 |
396 twCx.tlOnDisplay = trimFDS(flattenDateStruct(twCx.timeline, twCx.timeLevel)); |
425 twCx.tlOnDisplay = trimFDS(flattenDateStruct(twCx.timeline, twCx.timeLevel)); |
397 twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length; |
426 twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length; |
398 var maxTweets = 0, |
427 var maxTweets = 0, |
399 startTl = 0, |
428 startTl = 0, |
400 endTl = 0, |
429 endTl = 0, |
401 startTw = new Date(twCx.tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].created_at).valueOf(), |
430 startTw = twCx.tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].date_value, |
402 endTw = new Date(twCx.tweets[tweetsOnDisplay[0]].created_at).valueOf(); |
431 endTw = twCx.tweets[tweetsOnDisplay[0]].date_value; |
403 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
432 for (var i = 0; i < twCx.tlOnDisplay.length; i++) { |
404 if (startTw >= twCx.tlOnDisplay[i].start && startTw < twCx.tlOnDisplay[i].end) { |
433 if (startTw >= twCx.tlOnDisplay[i].start && startTw < twCx.tlOnDisplay[i].end) { |
405 startTl = i; |
434 startTl = i; |
406 } |
435 } |
407 if (endTw >= twCx.tlOnDisplay[i].start && endTw < twCx.tlOnDisplay[i].end) { |
436 if (endTw >= twCx.tlOnDisplay[i].start && endTw < twCx.tlOnDisplay[i].end) { |
434 } |
463 } |
435 twCx.scaleX = ( twCx.tlWidth - twCx.deltaX ) / maxTweets; |
464 twCx.scaleX = ( twCx.tlWidth - twCx.deltaX ) / maxTweets; |
436 tlPaper.clear(); |
465 tlPaper.clear(); |
437 relHover = null; |
466 relHover = null; |
438 |
467 |
|
468 // Dessin de la correspondance liste-timeline |
|
469 |
|
470 var startY = twCx.tlHeight - startTl * twCx.scaleY, |
|
471 endY = twCx.tlHeight - ( endTl + 1 ) * twCx.scaleY, |
|
472 path = "M0 " + twCx.tlHeight + "C" + .7*twCx.deltaX + " " + twCx.tlHeight + " " + .3*twCx.deltaX + " " + startY + " " + twCx.deltaX + " " + startY + "L" + twCx.tlWidth + " " + startY + "L" + twCx.tlWidth + " " + endY + "L" + twCx.deltaX + " " + endY + "C" + .3*twCx.deltaX + " " + endY + " " + .7*twCx.deltaX + " 0 0 0"; |
|
473 tlPaper.path( path ).attr({ "stroke" : "none", "fill" : "#000080", "opacity" : .2 }); |
|
474 |
439 // dessin de la date de début |
475 // dessin de la date de début |
440 |
476 |
441 tlPaper.text(2, twCx.tlHeight - 7, new Date(twCx.tlOnDisplay[0].start).toTimeString().substr(0,5)) |
477 tlPaper.text(2, twCx.tlHeight - 7, new Date(twCx.tlOnDisplay[0].start).toTimeString().substr(0,5)) |
442 .attr({ "text-anchor" : "start", "font-size": "9px" }); |
478 .attr({ "text-anchor" : "start", "font-size": "9px" }); |
443 |
479 |
464 tlPaper.path("M0 "+posY+"L" + twCx.tlWidth +" "+posY).attr({"stroke":"#ccc"}); |
500 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" }); |
501 tlPaper.text(2, posY, new Date(twCx.tlOnDisplay[i].end).toTimeString().substr(0,5)).attr({ "text-anchor" : "start", "font-size": "9px" }); |
466 } |
502 } |
467 } |
503 } |
468 |
504 |
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 |
505 // dessin du tweet courant |
477 |
506 |
|
507 |
478 var posp = tlPosTweet(twCx.tweets[p]); |
508 var posp = tlPosTweet(twCx.tweets[p]); |
479 if (posp) { |
509 if (posp) { |
480 |
510 |
481 drawTweetPos(posp, "#ffff00"); |
511 drawTweetPos(posp, "#ffff00"); |
|
512 var yy = posp.y - .5 * twCx.scaleY, |
|
513 path = "M0 " + ppy + "C" + ( .7 * twCx.deltaX ) + " " + ppy + " " + ( .2 * twCx.deltaX ) + " " + yy + " " + ( twCx.deltaX ) + " " + yy + "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy; |
|
514 yy = posp.y + .5 * twCx.scaleY; |
|
515 ppy += 84; |
|
516 path += "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy + "L" + twCx.deltaX + " " + yy + "C" + ( .2 * twCx.deltaX ) + " " + yy + " " + ( .7 * twCx.deltaX ) + " " + ppy + " 0 " + ppy; |
|
517 tlPaper.path( path ).attr({"stroke":"#ffff00", "fill" : "#ffff00", "fill-opacity" : .2}); |
482 |
518 |
483 // dessin des liens entre tweets |
519 // dessin des liens entre tweets |
484 |
520 |
485 function tweetAndArc(a, b, aorb) { |
521 function tweetAndArc(a, b, aorb) { |
486 if (a && b) { |
522 if (a && b) { |
513 } |
549 } |
514 } |
550 } |
515 |
551 |
516 } |
552 } |
517 |
553 |
|
554 } |
|
555 |
|
556 function clicTl(evt) { |
|
557 var o = $("#timeline").offset(); |
|
558 if (twCx.tlMouseClicked && twCx.tlMouseMoved) { |
|
559 var twid = tlIdFromPos(evt.pageX - o.left + twCx.refPosTl.x - twCx.refMouse.x, evt.pageY - o.top + twCx.refPosTl.y - twCx.refMouse.y, true); |
|
560 if (twid) { |
|
561 selectTweet(twid.id); |
|
562 } |
|
563 } else { |
|
564 var twid = tlIdFromPos(evt.pageX - o.left, evt.pageY - o.top, twCx.tlMouseClicked); |
|
565 if (twCx.tlMouseMoved && !twCx.tlMouseClicked) { |
|
566 if (twid) { |
|
567 rolloverTweet(twid.id, twid.annotation); |
|
568 } else { |
|
569 $("#hovertweet").hide(); |
|
570 } |
|
571 } |
|
572 if (twCx.tlMouseClicked && !twCx.tlMouseMoved) { |
|
573 if (twid) { |
|
574 selectTweet(twid.id); |
|
575 } |
|
576 } |
|
577 } |
518 } |
578 } |
519 |
579 |
520 $(document).ready(function() { |
580 $(document).ready(function() { |
521 tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight); |
581 tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight); |
522 socket = io.connect('http://' + document.location.hostname ); |
582 socket = io.connect('http://' + document.location.hostname ); |
568 wheelDelta = 0; |
628 wheelDelta = 0; |
569 } |
629 } |
570 return false; |
630 return false; |
571 }); |
631 }); |
572 $("#timeline, #tweetlist").mouseout(function() { |
632 $("#timeline, #tweetlist").mouseout(function() { |
|
633 twCx.tlMouseClicked = false; |
|
634 twCx.tlMouseMoved = false; |
573 $("#hovertweet").hide(); |
635 $("#hovertweet").hide(); |
574 }); |
636 }); |
575 $("#timeline").mousemove(function(evt) { |
637 $("#timeline").mousemove(function(evt) { |
576 var twid = tlIdFromPos(evt.offsetX, evt.offsetY); |
638 twCx.tlMouseMoved = true; |
577 if (twid) { |
639 clicTl(evt); |
578 rolloverTweet(twid.id, twid.annotation); |
640 }); |
579 } else { |
641 $("#timeline").mousedown(function(evt) { |
580 $("#hovertweet").hide(); |
642 twCx.tlMouseClicked = true; |
581 } |
643 twCx.tlMouseMoved = false; |
582 }); |
644 var o = $(this).offset(); |
583 $("#timeline").click(function(evt) { |
645 twCx.refMouse = { x : evt.pageX - o.left, y : evt.pageY - o.top }; |
584 var twid = tlIdFromPos(evt.offsetX, evt.offsetY); |
646 twCx.refPosTl = tlPosTweet(twCx.tweets[twCx.position]) || twCx.refMouse; |
585 if (twid) { |
647 }); |
586 selectTweet(twid.id); |
648 $("#timeline").mouseup(function(evt) { |
587 } |
649 clicTl(evt); |
|
650 twCx.tlMouseClicked = false; |
|
651 twCx.tlMouseMoved = false; |
588 }); |
652 }); |
589 }); |
653 }); |