diff -r c17ae1e12a46 -r b3f66379fed7 tweetcast/nodejs-bis/client/js/script.js --- a/tweetcast/nodejs-bis/client/js/script.js Wed Nov 16 17:47:11 2011 +0100 +++ b/tweetcast/nodejs-bis/client/js/script.js Thu Nov 17 19:08:32 2011 +0100 @@ -14,9 +14,9 @@ 60 * 1000, 15 * 1000 ], - timeLevel : 2, - deltaX : 30, - tlWidth : 98, + timeLevel : 0, + deltaX : 40, + tlWidth : 150, tlHeight : 450, globalWords : {}, refMouse : { x : 0, y : 0}, @@ -25,10 +25,16 @@ tlMouseClicked : false }, tlBuffer = '', - relHover = null, + relHover = [], wheelDelta = 0, - rx_word = /[^ \.&;,'"!\?@#\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g, - stop_list = [ 'and', 'les', 'the' ]; + rx_url = /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g, + rx_word = /[^ \.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g, + stop_list = [ 'and', 'avec', 'aux', 'car', 'comme', 'dans', 'donc', 'des', 'elle', 'est', 'être', 'eux', 'ils', 'les', 'leur', 'leurs', 'mes', 'mon', 'tes', 'ton', 'notre', 'nos', 'nous', 'ont', 'pas', 'que', 'qui', 'sont', 'the', 'une', 'votre', 'vos', 'vous' ]; + +function getColor(annotation, lum) { +// console.log(annotations[annotation].h, annotations[annotation].s, lum, Raphael.hsl(annotations[annotation].h, annotations[annotation].s, lum)) + return Raphael.hsl(annotations[annotation].colors.h, annotations[annotation].colors.s, lum); +} function arc(source, target) { var x3 = .3 * target.y - .3 * source.y + .8 * source.x + .2 * target.x; @@ -38,20 +44,6 @@ return "M" + source.x + " " + source.y + "C" + [x3, y3, x4, y4, target.x, target.y].join(" "); } -function countWords(text, wordobj) { - var tab = text.replace(/https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,'').match(rx_word); - for (var i in tab) { - var word = tab[i].toLowerCase(); - if (stop_list.indexOf(word) == -1 && tracking_keywords.indexOf(word) == -1) { - if (wordobj[word]) { - wordobj[word]++; - } else { - wordobj[word] = 1; - } - } - } -} - function addTweet(tweet) { function backRef(source_id, target_id, type) { var target = tweetById(target_id); @@ -68,6 +60,24 @@ } } + if (twCx.idIndex.indexOf(tweet.id) != -1) { + return; + } + + var txt_date = tweet.created_at; + if (navigator.userAgent.search(/MSIE/) != -1) { + txt_date = txt_date.replace(/( \+)/, ' UTC$1'); + } + tweet.date_value = Date.parse(txt_date); + + var tab = tweet.text.match(/\&\#[0-9]+\;/g); + for (var i in tab) { + var n = parseInt(tab[i].substr(2)); + if (n != NaN) { + tweet.text = tweet.text.replace(tab[i], String.fromCharCode(n)); + } + } + var ann = []; for (var j in annotations) { if (j != "default") { @@ -80,17 +90,6 @@ } } tweet.annotations = ann; - - var tab = tweet.text.match(/\&\#[0-9]+\;/g); - for (var i in tab) { - var n = parseInt(tab[i].substr(2)); - if (n != NaN) { - tweet.text = tweet.text.replace(tab[i], String.fromCharCode(n )); - } - } - - twCx.tweets.push(tweet); - twCx.idIndex.push(tweet.id); if (tweet.in_reply_to_status_id) { backRef( tweet.id, tweet.in_reply_to_status_id, "reply" ); @@ -99,15 +98,37 @@ backRef( tweet.id, tweet.retweeted_status.id, "retweet" ); } - countWords(tweet.text, twCx.globalWords); + var localWords = [] - var creadate = tweet.date_value; + var tab = tweet.text.replace(rx_url,'').match(rx_word); + for (var i in tab) { + var word = tab[i].toLowerCase(); + if (stop_list.indexOf(word) == -1 && tracking_keywords.indexOf(word) == -1) { + if (twCx.globalWords[word]) { + twCx.globalWords[word]++; + } else { + twCx.globalWords[word] = 1; + } + localWords.push(word); + } + } + + tweet.words = localWords; + + var p = twCx.idIndex.length; + while (p && tweet.id < twCx.idIndex[p-1]) { + p--; + } + twCx.tweets.splice(p, 0, tweet); + twCx.idIndex.splice(p, 0, tweet.id); + if (!twCx.timeline.length) { - twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(creadate / twCx.date_levels[0])) ] + twCx.timeline = [ populateDateStruct(0, twCx.date_levels[0] * parseInt(tweet.date_value / twCx.date_levels[0])) ] } - while (creadate > twCx.timeline[twCx.timeline.length - 1].end) { + while (tweet.date_value > twCx.timeline[twCx.timeline.length - 1].end) { twCx.timeline.push( populateDateStruct(0, twCx.timeline[twCx.timeline.length - 1].end) ); } + insertIntoDateStruct(twCx.timeline, tweet); } @@ -145,11 +166,16 @@ return result; } -function trimFDS(slices) { - while (slices[0].tweets.length == 0) { +function trimFDS() { + var slices = flattenDateStruct(twCx.timeline, twCx.timeLevel), + delta = 30 * twCx.date_levels[twCx.timeLevel], + centre = Math.min(slices[slices.length - 1].end - delta , Math.max(slices[0].start + delta, twCx.tweets[twCx.position].date_value)), + min = centre - delta, + max = centre + delta; + while (slices[0].start < min) { slices.splice(0,1); } - while (slices[slices.length - 1].tweets.length == 0) { + while (slices[slices.length - 1].end > max) { slices.pop(); } return slices; @@ -207,6 +233,16 @@ } } +function goToPos(nPos) { + twCx.position = Math.min( twCx.tweets.length - 1, Math.max(0, nPos ) ); + twCx.followLast = (twCx.position == twCx.tweets.length - 1); + updateDisplay(); +} + +function movePos(delta) { + goToPos( delta + twCx.position ); +} + function tweetToHtml(tweet, className, elName) { if (!tweet) { return placeHolder(className); @@ -214,10 +250,9 @@ var el = (elName ? elName : 'li'); var html = '<' + el + ' class="tweet ' + className + '" id="tweet_' + tweet.id + '"'; if (className != 'full') { - html += ' onclick="selectTweet(\'' + tweet.id + '\'); return false;" onmouseover="rolloverTweet(\'' + tweet.id + '\');"'; - } else { - html += ' onmouseover="$(\'#hovertweet\').hide();"'; + html += ' onclick="selectTweet(\'' + tweet.id + '\'); return false;"'; } + html += ' onmouseover="rolloverTweet(\'' + tweet.id + "', " + ( className == 'icons' ) + ');"'; if (twCx.followLast && className == 'full' && el == 'li') { html += ' style="display: none"'; } @@ -225,7 +260,7 @@ if (tweet.annotations.length) { html += '
'; for (var i in tweet.annotations) { - html += '
'; + html += '
'; } html += '
'; } @@ -233,7 +268,7 @@ a_user = ''; html += '
' + a_user + ''; if (className == 'full') { - html += '

' + new Date(tweet.date_value).toLocaleTimeString() + '

'; + html += '

' + new Date(tweet.date_value).toTimeString().substr(0,8) + '

'; } html += '
'; if (className != 'icons') { @@ -248,10 +283,15 @@ }); } for (var i in tweet.entities.urls) { + var linkurl = ( tweet.entities.urls[i].expanded_url ? tweet.entities.urls[i].expanded_url : tweet.entities.urls[i].url ), + dispurl = linkurl.replace(/https?:\/\//,''); + if (linkurl.search(/https?:\/\//) == -1) { + linkurl = 'http://' + linkurl; + } entities.push({ "start" : tweet.entities.urls[i].indices[0], "end" : tweet.entities.urls[i].indices[1], - "html" : '' + tweet.entities.urls[i].expanded_url + '' + "html" : '' + dispurl + '' }); } for (var i in tweet.entities.user_mentions) { @@ -274,6 +314,9 @@ } function tlIdFromPos(x, y, outside) { + if (!twCx.tlOnDisplay) { + return; + } var ligne = Math.min( twCx.tlOnDisplay.length - 1, Math.max( 0, Math.floor(( twCx.tlHeight - y ) / twCx.scaleY) ) ), colonne = Math.floor(( x - twCx.deltaX ) / twCx.scaleX ), l = 0; @@ -321,29 +364,77 @@ return ( x && y ? { "x" : x, "y" : y } : null); } -function rolloverTweet(tweetid, annotation) { - var t = tweetById(tweetid), - p = tlPosTweet(t, annotation); - if (t && p) { - var ptl = $("#timeline").offset(); +function rolloverTweet(tweetid, showPopup, annotation) { + var t = tweetById(tweetid); + if (!t) { + return; + } + var p = tlPosTweet(t, annotation); + if (!p) { + return; + } + var ptl = $("#timeline").offset(); + if (showPopup) { $("#hovercontent").html(tweetToHtml(t, 'full', 'div')); $("#hovertweet").css({ "left" : parseInt(ptl.left + p.x) + "px", "top" : parseInt(ptl.top + p.y), "display" : "block"}); - if (relHover) { - relHover.remove(); - } - relHover = drawTweetPos(p, '#ffffff') + } else { + $("#hovertweet").hide(); } + for (var i in relHover) { + relHover[i].remove(); + } + relHover = drawTweetArcs(t, p, '#303030'); + relHover.push(drawTweetPos(p, '#ffffff')); } function drawTweetPos(pos, color) { var rel = tlPaper.rect(pos.x - .5 * twCx.scaleX, pos.y - .5 * twCx.scaleY, twCx.scaleX, twCx.scaleY); - rel.attr({ "stroke" : color, "fill" : color, "fill-opacity" : .1 }); + rel.attr({ "stroke" : color, "fill" : color, "fill-opacity" : .25 }); return rel; } +function drawTweetArcs(tweet, pos, color) { + + var res = []; + + function tweetAndArc(a, b, aorb) { + if (a && b) { + res.push(drawTweetPos(aorb ? a : b, color)); + var aa = tlPaper.path(arc(a,b)) + .attr({ "stroke" : color, "stroke-width" : 1.5, "stroke-opacity" : .8 }); + res.push(aa); + } + } + + if (tweet.retweeted_status) { + var t = tweetById(tweet.retweeted_status.id); + if (t) { + tweetAndArc(pos, tlPosTweet(t)); + } + } + + if (tweet.in_reply_to_status_id) { + var t = tweetById(tweet.in_reply_to_status_id); + if (t) { + tweetAndArc(pos, tlPosTweet(t)); + } + } + + if (tweet.backRefs) { + for (var i in tweet.backRefs) { + var t = tweetById(tweet.backRefs[i].referenced_by_id); + if (t) { + tweetAndArc(tlPosTweet(t), pos, true); + } + } + } + + return res; +} + function updateDisplay() { var p = twCx.position, l = twCx.tweets.length, @@ -352,12 +443,43 @@ html = '', tweetsOnDisplay = [], localWords = {}; - + + if (p == 0) { + $("#downbutton").addClass("inactive"); + } else { + $("#downbutton").removeClass("inactive"); + } + + if (p == l-1) { + $("#upbutton").addClass("inactive"); + } else { + $("#upbutton").removeClass("inactive"); + } + + function pushTweet(tp, className) { if (tp < l && tp >= 0) { html += tweetToHtml(twCx.tweets[tp], className) tweetsOnDisplay.push(tp); - countWords(twCx.tweets[tp].text, localWords); + for (var i in twCx.tweets[tp].words) { + var w = twCx.tweets[tp].words[i]; + if (localWords[w]) { + localWords[w].freq++ + } else { + localWords[w] = { + "freq" : 1, + "annotations" : {} + } + for (var j in annotations) { + if (j != 'default') { + localWords[w].annotations[j] = 0; + } + } + } + for (var j in twCx.tweets[tp].annotations) { + localWords[w].annotations[twCx.tweets[tp].annotations[j]]++; + } + } } else { html += placeHolder(className); } @@ -407,26 +529,45 @@ } for (var j in localWords) { - if (localWords[j] < 2) delete localWords[j]; + if (localWords[j].freq < 2) delete localWords[j]; } var tab = []; for (var j in localWords) { tab.push({ "word": j, - "freq" : localWords[j] / Math.log(1+twCx.globalWords[j]) + "freq" : localWords[j].freq, + "annotations" : localWords[j].annotations, + "score" : localWords[j].freq / Math.log(1+twCx.globalWords[j]) }); } - tab.sort( function(a,b){ return ( b.freq - a.freq ) }).splice(15); - var minfreq = tab[tab.length - 1].freq, - maxfreq = Math.max(minfreq + .1, tab[0].freq), - echfreq = 8 / Math.sqrt( maxfreq - minfreq ); - $("#motscles").html(tab.map(function(t) { return '' + t.word + '' }).join(" ")) + tab.sort( function(a,b){ return ( b.score - a.score ) }).splice(20); + var minfreq = tab[tab.length - 1].score, + maxfreq = Math.max(minfreq + .1, tab[0].score), + echfreq = 8 / Math.sqrt( maxfreq - minfreq ), + html = ''; + for (var j in tab) { + var maxann = 0, + ann = "default"; + for (var k in tab[j].annotations) { + if (tab[j].annotations[k] == maxann) { + ann = "default"; + } + if (tab[j].annotations[k] > maxann) { + ann = k; + maxann = tab[j].annotations[k]; + } + } + var coul = (ann == "default" ? '' : ' background: ' + getColor(ann, 1 - .4 * ( tab[j].annotations[ann] / tab[j].freq ) ) + ';"'), + fontsize = Math.floor( ( 12 + Math.sqrt( tab[j].score - minfreq ) * echfreq ) ); + html += '' + tab[j].word + ' '; + } + $("#motscles").html(html); - twCx.tlOnDisplay = trimFDS(flattenDateStruct(twCx.timeline, twCx.timeLevel)); + twCx.tlOnDisplay = trimFDS(); twCx.scaleY = twCx.tlHeight / twCx.tlOnDisplay.length; var maxTweets = 0, startTl = 0, - endTl = 0, + endTl = twCx.tlOnDisplay.length - 1, startTw = twCx.tweets[tweetsOnDisplay[tweetsOnDisplay.length - 1]].date_value, endTw = twCx.tweets[tweetsOnDisplay[0]].date_value; for (var i = 0; i < twCx.tlOnDisplay.length; i++) { @@ -489,14 +630,14 @@ var l = twCx.tlOnDisplay[i].displayData[j].length; if (l > 0) { tlPaper.rect( twCx.deltaX + n * twCx.scaleX, posY, l * twCx.scaleX, twCx.scaleY ) - .attr({"stroke" : "none", "fill" : annotations[j].colors.timeline }); + .attr({"stroke" : "none", "fill" : getColor(j, .4) }); n += l; } } // Si on est à une demi-heure, on trace un axe secondaire + heure - if (i < twCx.tlOnDisplay.length - 1 && !(new Date(twCx.tlOnDisplay[i].end).valueOf() % 1800000)) { + if (i < twCx.tlOnDisplay.length - 1 && !(twCx.tlOnDisplay[i].end % 1800000)) { tlPaper.path("M0 "+posY+"L" + twCx.tlWidth +" "+posY).attr({"stroke":"#ccc"}); tlPaper.text(2, posY, new Date(twCx.tlOnDisplay[i].end).toTimeString().substr(0,5)).attr({ "text-anchor" : "start", "font-size": "9px" }); } @@ -514,41 +655,9 @@ yy = posp.y + .5 * twCx.scaleY; ppy += 84; path += "L" + ( posp.x - .5 * twCx.scaleX ) + " " + yy + "L" + twCx.deltaX + " " + yy + "C" + ( .2 * twCx.deltaX ) + " " + yy + " " + ( .7 * twCx.deltaX ) + " " + ppy + " 0 " + ppy; - tlPaper.path( path ).attr({"stroke":"#ffff00", "fill" : "#ffff00", "fill-opacity" : .2}); - - // dessin des liens entre tweets - - function tweetAndArc(a, b, aorb) { - if (a && b) { - drawTweetPos(aorb ? a : b, "#00c000"); - tlPaper.path(arc(a,b)) - .attr({ "stroke" : "#e000e0", "stroke-width" : 1.5 }); - } - } + tlPaper.path( path ).attr({"stroke":"#ffff00", "fill" : "#ffff00", "fill-opacity" : .15}); - if (twCx.tweets[p].retweeted_status) { - var t = tweetById(twCx.tweets[p].retweeted_status.id); - if (t) { - tweetAndArc(posp, tlPosTweet(t)); - } - } - - if (twCx.tweets[p].in_reply_to_status_id) { - var t = tweetById(twCx.tweets[p].in_reply_to_status_id); - if (t) { - tweetAndArc(posp, tlPosTweet(t)); - } - } - - if (twCx.tweets[p].backRefs) { - for (var i in twCx.tweets[p].backRefs) { - var t = tweetById(twCx.tweets[p].backRefs[i].referenced_by_id); - if (t) { - tweetAndArc(tlPosTweet(t), posp, true); - } - } - } - + drawTweetArcs(twCx.tweets[p], posp, '#800080'); } } @@ -564,7 +673,7 @@ var twid = tlIdFromPos(evt.pageX - o.left, evt.pageY - o.top, twCx.tlMouseClicked); if (twCx.tlMouseMoved && !twCx.tlMouseClicked) { if (twid) { - rolloverTweet(twid.id, twid.annotation); + rolloverTweet(twid.id, true, twid.annotation); } else { $("#hovertweet").hide(); } @@ -577,38 +686,49 @@ } } +function loadTweets(tweets) { + twCx.timeline = []; + twCx.idIndex = []; + twCx.tweets = []; + for (var i in tweets) { + addTweet(tweets[i]); + } + if (twCx.followLast) { + twCx.position = twCx.tweets.length - 1; + } + updateDisplay(); +} + $(document).ready(function() { tlPaper = Raphael("timeline", twCx.tlWidth, twCx.tlHeight); - socket = io.connect('http://' + document.location.hostname ); - socket.on("initial_data", function(data) { - twCx.timeline = []; - twCx.idIndex = []; - twCx.tweets = []; - for (var i in data.tweets) { - addTweet(data.tweets[i]); - } - if (twCx.followLast) { - twCx.position = twCx.tweets.length - 1; - } - updateDisplay(); - }); - socket.on("update", function(data) { - for (var i in data.new_tweets) { - addTweet(data.new_tweets[i]); - } - if (twCx.followLast) { - twCx.position = twCx.tweets.length - 1; - } - updateDisplay(); - }); + + if (typeof STANDALONE_APP == "undefined" || !STANDALONE_APP) { + console.log("Loading from socket.io"); + socket = io.connect('http://' + document.location.hostname ); + socket.on("initial_data", function(data) { + loadTweets(data.tweets) + }); + socket.on("update", function(data) { + if (!twCx.tweets) { + return; + } + for (var i in data.new_tweets) { + addTweet(data.new_tweets[i]); + } + if (twCx.followLast) { + twCx.position = twCx.tweets.length - 1; + } + updateDisplay(); + }); + } else { + $.getScript("tweetdata.js"); + } $("#tweetlist").mousewheel(function(e, d) { wheelDelta += d; if (Math.abs(wheelDelta) >= 1) { - twCx.position = Math.min( twCx.tweets.length - 1, Math.max(0, parseInt(wheelDelta) + twCx.position ) ); - twCx.followLast = (twCx.position == twCx.tweets.length - 1); - updateDisplay(); + movePos( parseInt(wheelDelta) ); wheelDelta = 0; } return false; @@ -650,4 +770,9 @@ twCx.tlMouseClicked = false; twCx.tlMouseMoved = false; }); -}); \ No newline at end of file + $("#scrollcont").scroll(function(evt) { + var p = Math.floor( twCx.tweets.length * ( 1 - $(this).scrollTop() / 9548 ) ); + goToPos(p); + }) +}); +