Merge with 02b196db08bc1a1c5b1ae94bc35ae4d84f6f1365
authorYves-Marie Haussonne <1218002+ymph@users.noreply.github.com>
Mon, 21 Nov 2011 11:02:05 +0100
changeset 381 b638caf3b65f
parent 379 083320a74eb2 (current diff)
parent 380 b3f66379fed7 (diff)
child 386 d8bc1dc21ae4
Merge with 02b196db08bc1a1c5b1ae94bc35ae4d84f6f1365
--- a/tweetcast/nodejs-bis/client/css/style.css	Mon Nov 21 11:00:34 2011 +0100
+++ b/tweetcast/nodejs-bis/client/css/style.css	Mon Nov 21 11:02:05 2011 +0100
@@ -25,15 +25,19 @@
 	font-family: Helvetica, Arial, sans-serif;
 }
 
-/* Liste de Tweets */
-
 #container {
     margin: 20px;
 }
 
+/* Barre */
+.barre  {
+    float: left; width: 100%;
+}
+
+/* Liste de Tweets */
 
 #tweetlist {
-	float: left; width: 280px; height: 450px; list-style: none; border: 1px solid #999; color: #585858;
+	float: left; width: 280px; height: 450px; list-style: none; border: 1px solid #999; color: #585858; cursor: pointer; cursor: hand;
 }
 
 li.tweet, li.placeholder {
@@ -41,7 +45,7 @@
 }
 
 li.tweet {
-	position: relative; background: #fff; cursor: pointer;
+	position: relative; background: #fff;
 }
 
 li.full {
@@ -89,7 +93,7 @@
 }
 
 .full p.created_at {
-	font-size: 12px; text-align: center; font-style: italic; color: #787878;
+	font-size: 12px; text-align: center; font-style: italic; color: #999999; width: 58px; overflow: hidden;
 }
 
 .half p.created_at, .icons p {
@@ -101,7 +105,7 @@
 }
 
 .icons .annotations {
-    position: absolute; width: 20px; height: 4px; left: 0; top: 18px; z-index: 3;
+    position: absolute; width: 20px; height: 6px; left: 0; top: 16px; z-index: 3;
 }
 
 div.annotation {
@@ -115,7 +119,17 @@
 /* timeline */
 
 #timeline {
-    float: left; height: 450px; width: 98px; border-width: 1px; border-style: solid solid solid none; border-color: #999;
+    float: left; height: 450px; width: 150px; border-width: 1px; border-style: solid solid solid none; border-color: #999;  cursor: pointer; cursor: hand;
+}
+
+
+
+#scrollcont {
+    float: left; width: 20px; height: 452px; overflow: auto;
+}
+
+#scrollin {
+    width: 1px; height: 10000px;
 }
 
 /* hovertweet */
@@ -131,3 +145,9 @@
 #hoverarrow {
     position: absolute; width: 10px; height: 18px; left: -9px; top: 13px;  background: url(../img/arrow.png);
 }
+
+/* Tag Cloud */
+
+#motscles {
+    float: left; width: 400px; height: 100px; text-align: center; border: 1px solid #999; margin: 0 20px;
+}
--- a/tweetcast/nodejs-bis/client/index.html	Mon Nov 21 11:00:34 2011 +0100
+++ b/tweetcast/nodejs-bis/client/index.html	Mon Nov 21 11:02:05 2011 +0100
@@ -4,8 +4,12 @@
         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
         <title>Polemic Tweet “TweetCast”</title>
         <script type="text/javascript" src="js/jquery-1.6.4.min.js"></script>
-        <script type="text/javascript" src="/config"></script>
-        <script type="text/javascript" src="/socket.io/socket.io.js"></script>
+        <script type="text/javascript" src="config.js"></script>
+        <script type="text/javascript">
+        if (typeof STANDALONE_APP == "undefined" || !STANDALONE_APP) {
+            document.write('<script type="text/javascript" src="/socket.io/socket.io.js"><' + '/script>');
+        }
+        </script>
         <script type="text/javascript" src="js/raphael-min.js"></script>
         <script type="text/javascript" src="js/jquery.mousewheel.min.js"></script>
         <script type="text/javascript" src="js/script.js"></script>
@@ -13,14 +17,23 @@
     </head>
     <body>
         <div id="container">
+            <div class="barre">
+                <div id="upbutton" onclick="movePos(1)"></div>
+            </div>
             <ul id="tweetlist">
             </ul>
             <div id="timeline"></div>
+            <div id="scrollcont">
+                <div id="scrollin"></div>
+            </div>
+            <div class="barre">
+                <div id="downbutton" onclick="movePos(-1)"></div>
+            </div>
+        </div>
             <div id="motscles"></div>
             <div id="hovertweet">
                 <div id="hovercontent"></div>
                 <div id="hoverarrow"></div>
             </div>
-        </div>
     </body>
 </html>
\ No newline at end of file
--- a/tweetcast/nodejs-bis/client/js/script.js	Mon Nov 21 11:00:34 2011 +0100
+++ b/tweetcast/nodejs-bis/client/js/script.js	Mon Nov 21 11:02:05 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 += '<div class="annotations">';
         for (var i in tweet.annotations) {
-            html += '<div class="annotation" style="width:' + (100/tweet.annotations.length) + '%; background:' + annotations[tweet.annotations[i]].colors[(className == 'icons' ? 'timeline' : 'tweet')] + '"></div>';
+            html += '<div class="annotation" style="width:' + (100/tweet.annotations.length) + '%; background:' + getColor(tweet.annotations[i], (className == 'icons' ? .4 : .8)) + '"></div>';
         }
         html += '</div>';
     }
@@ -233,7 +268,7 @@
     a_user = '<a href="http://twitter.com/' + tweet.user.screen_name + '" var target="_blank" title="' + tweet.user.name + '">';
     html += '<div class="around_img">' + a_user + '<img class="profile_image" src="' + tweet.user.profile_image_url + '" /></a>';
     if (className == 'full') {
-        html += '<p class="created_at">' + new Date(tweet.date_value).toLocaleTimeString() + '</p>';
+        html += '<p class="created_at">' + new Date(tweet.date_value).toTimeString().substr(0,8) + '</p>';
     }
     html += '</div>';
     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" : '<a href="' + tweet.entities.urls[i].expanded_url + '" target="_blank">' + tweet.entities.urls[i].expanded_url + '</a>'
+                "html" : '<a href="' + linkurl  + '" target="_blank">' + dispurl + '</a>'
             });
         }
         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 '<span style="font-size: ' + Math.floor( ( 12 + Math.sqrt( t.freq - minfreq ) * echfreq ) ) + 'px">' + t.word + '</span>' }).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 += '<span style="padding: 2px; line-height: ' + (8 + fontsize) + 'px; font-size: ' + fontsize + 'px;' + coul + '">' + tab[j].word + '</span> ';
+    }
+    $("#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);
+    })
+});
+
--- a/tweetcast/nodejs-bis/conf.js	Mon Nov 21 11:00:34 2011 +0100
+++ b/tweetcast/nodejs-bis/conf.js	Mon Nov 21 11:02:05 2011 +0100
@@ -1,5 +1,8 @@
 /* CONFIGURATION FILE USED BY BOTH CLIENT AND SERVER */
 
+RECORD_NEW_TWEETS = false;
+STANDALONE_APP = true;
+
 var app_port = 8000,
     tracking_keywords = [ "sarkozy" ],
     annotations = {
--- a/tweetcast/nodejs-bis/tweetcast.js	Mon Nov 21 11:00:34 2011 +0100
+++ b/tweetcast/nodejs-bis/tweetcast.js	Mon Nov 21 11:02:05 2011 +0100
@@ -1,5 +1,5 @@
 READ_OLD_TWEETS = true;
-RECORD_NEW_TWEETS = false;
+RECORD_NEW_TWEETS = true;
 var conf_file = flagOption('-c', 'conf.js');
 console.log('Reading Configuration from ' + conf_file);
 var fs = require('fs');
@@ -77,16 +77,17 @@
         'utc_offset'
     ],
     content_types = {
-        css : "text/css",
-        html : "text/html",
-        js : "text/javascript",
+        css : "text/css; charset=utf-8",
+        html : "text/html; charset=utf-8",
+        js : "text/javascript; charset=utf-8",
         png : "image/png"
     };
 
 function httpHandler(req, res) {
     console.log("HTTP Request for URL "+req.url);
-    var url = ( req.url == "/config" ? conf_file : __dirname + "/client" + req.url + ( req.url[req.url.length - 1] == "/" ? "index.html" : "" ) ),
-        ext = url.split('.').slice(-1)[0].toLowerCase();
+    var url = req.url.split('?')[0];
+    url = ( url == "/config.js" ? conf_file : __dirname + "/client" + url + ( url[url.length - 1] == "/" ? "index.html" : "" ) );
+    var ext = url.split('.').slice(-1)[0].toLowerCase();
     fs.readFile( url, function(err, data) {
         if (err) {
             console.log("Error 404");
@@ -110,7 +111,6 @@
         console.log("Error: Tweet already in list");
         return false;
     }
-    tweet.date_value = new Date(tweet.created_at).valueOf();
     tweets.push(tweet);
     tweet_ids.push(tweet.id);
     return true;
@@ -188,6 +188,7 @@
                                 delete tweet.retweeted_status[keys_to_delete[j]];
                             }
                         }
+                    /*    tweet.date_value = Date.parse(tweet.created_at); */
                         if (addToList(tweet)) {
                             writestream.write(JSON.stringify(tweet)+'\n');
                         }
@@ -213,10 +214,14 @@
 
 if (READ_OLD_TWEETS) {
     readTweetsFromFile(tweet_file);
+} else {
+    console.log("Reading old tweets disabled !");
 }
 
 if (RECORD_NEW_TWEETS) {
     requestTweets();
+} else {
+    console.log("Recording new tweets disabled !");
 }
 
 io.set('log level', 0);