tweetcast/nodejs/client/js/script.js
changeset 336 d60efd677b50
parent 326 c28048fb63b4
parent 335 5f83c21dee69
child 339 6a073c4a8578
equal deleted inserted replaced
330:1a77b44dfa7d 336:d60efd677b50
     1 var socket,
     1 var socket,
     2     paper,
     2     tlPaper,
       
     3     twPaper,
     3     tweetData = {
     4     tweetData = {
     4         "tweetcount" : 0,
     5         "tweetcount" : 0,
     5         "position" : -1,
     6         "position" : 0,
       
     7         "followLast" : true,
     6         "zoomLevel" : 3,
     8         "zoomLevel" : 3,
     7         "timeLevel" : 2
     9         "timeLevel" : 2,
       
    10         "tweets" : [],
       
    11         "posIndex" : [],
       
    12         "tlChanged" : true
     8     },
    13     },
     9     zoomLevels = [
    14     displaySplitting = [
    10         {
    15         {
    11             "description" : "160 tweets per page",
    16             positions : [ 10, 20, 22, 24, 25, 26, 27, 28, 29, 31, 33, 43, 53 ],
    12             "className" : "icons",
    17             classNames : [ 'icons fade', 'icons', 'quarter fade', 'quarter', 'half fade', 'half', 'full', 'half', 'half fade', 'quarter', 'quarter fade', 'icons', 'icons fade' ]
    13             "displayCount" : 160
       
    14         },
    18         },
    15         {
    19         {
    16             "description" : "24 tweets per page",
    20             positions : [ 1, 3, 5, 7, 13, 33, 53 ],
    17             "className" : "quarter",
    21             classNames : [ 'full', 'half', 'half fade', 'quarter', 'quarter fade', 'icons', 'icons fade' ]
    18             "displayCount" : 24
       
    19         },
       
    20         {
       
    21             "description" : "12 tweets per page",
       
    22             "className" : "half",
       
    23             "displayCount" : 12
       
    24         },
       
    25         {
       
    26             "description" : "6 tweets per page",
       
    27             "className" : "full",
       
    28             "displayCount" : 6
       
    29         }
    22         }
    30     ],
    23     ],
    31     colors = {
    24     blockUpdate = false,
    32         'positive' : "#1D973D",
    25     waitUpdate = true,
    33         'reference' : "#C5A62D",
    26     wheeldelta = 0;
    34         'negative' : "#CE0A15",
    27 
    35         'question' : "#036AAE"
    28 function placeHolder(className) {
    36     },
    29     return '<li class="placeholder ' + className + '"></li>';
    37     timeWindow;
    30 }
    38 
    31 
    39 function tweetToHtml(tweet) {
    32 function tweetToHtml(tweet, className) {
    40 	html = '<li class="tweet ' + zoomLevels[tweetData.zoomLevel].className;
    33 	html = '<li class="tweet ' + className + '" id="tweet_' + tweet.pos + '"';
    41 	for (var i in tweet.annotations) {
    34 	if (tweetData.followLast && tweet.pos == tweetData.position) {
    42 		html += ' a_' + tweet.annotations[i]
    35 	    html += ' style="display: none"';
    43 	}
    36 	}
    44 	html += '" id="tweet_' + tweet.id + '">';
    37 	html += '>';
       
    38 	if (tweet.annotations.length) {
       
    39 	    html += '<div class="annotations">';
       
    40 	    for (var i in tweet.annotations) {
       
    41     		html += '<div class="annotation" style="width:' + (100/tweet.annotations.length) + '%; background:' + annotations[tweet.annotations[i]].colors.tweet + '"></div>';
       
    42     	}
       
    43     	html += '</div>';
       
    44 	}
       
    45 	html += '<div class="twmain">';
    45 	a_user = '<a href="http://twitter.com/' + tweet.user.screen_name + '" target="_blank" title="' + tweet.user.name + '">';
    46 	a_user = '<a href="http://twitter.com/' + tweet.user.screen_name + '" target="_blank" title="' + tweet.user.name + '">';
    46 	if (tweet.user.profile_image_url) {
    47 	if (tweet.user.profile_image_url) {
    47 		html += a_user + '<img class="tweet_profile_image" src="' + tweet.user.profile_image_url + '" /></a>';
    48 		html += a_user + '<img class="profile_image" src="' + tweet.user.profile_image_url + '" /></a>';
    48 	}
    49 	}
    49 	html += '<h4>' + a_user + '@' + tweet.user.screen_name + '</a></h4><p class="tweet_created_at">' + new Date(tweet.created_at).toLocaleTimeString() + '</p><p>';
    50 	html += '<h4>' + a_user + '@' + tweet.user.screen_name + '</a></h4><p class="created_at">' + new Date(tweet.created_at).toLocaleTimeString() + '</p><p class="tweet_text">';
    50 	lastend = 0;
    51 	lastend = 0;
    51 	txt = '';
    52 	txt = '';
    52 	entities = [];
    53 	entities = [];
    53 	for (var i in tweet.entities.hashtag) {
    54 	for (var i in tweet.entities.hashtags) {
    54 	    entities.push({
    55 	    entities.push({
    55 	        "start" : tweet.entities.hashtag[i].indices[0],
    56 	        "start" : tweet.entities.hashtags[i].indices[0],
    56 	        "end" : tweet.entities.hashtag[i].indices[1],
    57 	        "end" : tweet.entities.hashtags[i].indices[1],
    57 	        "html" : '<a href="http://twitter.com/search?q=%23' + tweet.entities.hashtag[i].text + '" target="_blank">#' + tweet.entities.hashtag[i].text + '</a>'
    58 	        "html" : '<a href="http://twitter.com/search?q=%23' + tweet.entities.hashtags[i].text + '" target="_blank">#' + tweet.entities.hashtags[i].text + '</a>'
    58 	    });
    59 	    });
    59 	}
    60 	}
    60 	for (var i in tweet.entities.urls) {
    61 	for (var i in tweet.entities.urls) {
    61 	    entities.push({
    62 	    entities.push({
    62 	        "start" : tweet.entities.urls[i].indices[0],
    63 	        "start" : tweet.entities.urls[i].indices[0],
    75     for (var i in entities) {
    76     for (var i in entities) {
    76  		txt += tweet.text.substring(lastend, entities[i].start) + entities[i].html;
    77  		txt += tweet.text.substring(lastend, entities[i].start) + entities[i].html;
    77         lastend = entities[i].end;
    78         lastend = entities[i].end;
    78     }
    79     }
    79 	txt += tweet.text.substring(lastend);
    80 	txt += tweet.text.substring(lastend);
    80 	html += txt + '</p></li>';
    81 	html += txt + '</p></li></div>';
    81 	return html;
    82 	return html;
    82 }
    83 }
    83 
    84 
    84 function displayTweets() {
    85 
    85     var to = tweetData.position + 1,
    86 function getUpdate() {
    86         from = Math.max(0, to - zoomLevels[tweetData.zoomLevel].displayCount);
    87     tweetData.posToDisplay = [];
    87     socket.emit('getTweets',{ "from": from, "to": to, "callback" : "display" });
    88     if (tweetData.followLast) {
    88 }
    89         for (var i = tweetData.tweetcount; i >= tweetData.tweetcount - 52; i--) {
    89 
    90             tweetData.posToDisplay.push( i > 0 ? i : -1 );
    90 function setZoom(level) {
    91         }
    91     tweetData.zoomLevel = Math.max(0, Math.min( zoomLevels.length - 1 , level ) );
    92         tweetData.end = tweetData.tweetcount;
    92     displayTweets();
    93         tweetData.start = Math.max(1, tweetData.end - 52);
       
    94     } else {
       
    95         for (var i = tweetData.position + 26; i >= tweetData.position - 26; i--) {
       
    96             tweetData.posToDisplay.push( i > 0 && i <= tweetData.tweetcount ? i : -1 );
       
    97         }
       
    98         tweetData.end = Math.min(tweetData.tweetcount, tweetData.position + 26 );
       
    99         tweetData.start = Math.max(1, tweetData.position - 26);
       
   100     }
       
   101     var tweetsToGet = [];
       
   102     for (var i = tweetData.start; i <= tweetData.end; i++) {
       
   103         if (!tweetByPos(i)) {
       
   104             tweetsToGet.push(i);
       
   105         }
       
   106     }
       
   107     if (tweetsToGet.length) {
       
   108         socket.emit('updateTweets', {
       
   109             "tweets" : tweetsToGet
       
   110         });
       
   111     } else {
       
   112         drawTweetList();
       
   113         //drawDisplay();
       
   114     }
       
   115     if (tweetData.tlChanged) {
       
   116         socket.emit('updateTimeline', {
       
   117             "level" : tweetData.timeLevel
       
   118         });
       
   119     } else {
       
   120         drawTimeLine();
       
   121     }
       
   122 }
       
   123 
       
   124 function tweetByPos(pos) {
       
   125     var index = tweetData.posIndex.indexOf(pos);
       
   126     return (index == -1 ? false : tweetData.tweets[index]);
       
   127 }
       
   128 
       
   129 function delayedUpdate() {
       
   130     blockUpdate = false;
       
   131     if (waitUpdate) {
       
   132         updateDisplay();
       
   133     }
       
   134 }
       
   135 
       
   136 function updateDisplay() {
       
   137     if (blockUpdate) {
       
   138         waitUpdate = true;
       
   139     } else {
       
   140         waitUpdate = false;
       
   141         getUpdate();
       
   142         blockUpdate = true;
       
   143         setTimeout(delayedUpdate, 100);
       
   144     }
    93 }
   145 }
    94 
   146 
    95 function setTimeZoom(level) {
   147 function setTimeZoom(level) {
    96     tweetData.timeLevel = Math.max(0, Math.min( 3, level ));
   148     if (level >= 0 && level <= 4) {
    97     getTimeline();
   149         tweetData.timeLevel = level;
    98 }
   150         tweetData.tlChanged = true;
    99 
   151         updateDisplay();
   100 function getTimeline() {
   152     }
   101     socket.emit('getTimeline',{"level":tweetData.timeLevel});
   153 }
   102 }
   154 
   103 
   155 function drawTweetList() {
   104 function showTimeWindow() {
   156     html = '';
   105     if (timeWindow) {
   157     var i = 0;
   106         timeWindow.remove();
   158     while (i < tweetData.posIndex.length) {
   107         timeWindow = null;
   159         if (tweetData.posToDisplay.indexOf(tweetData.posIndex[i]) == -1) {
   108     }
   160             tweetData.posIndex.splice(i,1);
   109     if (tweetData.tweetsOnDisplay && tweetData.timelineOnDisplay) {
   161             tweetData.tweets.splice(i,1);
   110         var dtfintl = new Date ( tweetData.timelineOnDisplay[ tweetData.timelineOnDisplay.length - 1 ].end ),
   162         } else {
   111             dtdebtl = new Date ( tweetData.timelineOnDisplay[0].start ),
   163             i++;
   112             dtfintw = new Date( tweetData.tweetsOnDisplay[0].created_at ),
   164         }
   113             dtdebtw = new Date( tweetData.tweetsOnDisplay[ tweetData.tweetsOnDisplay.length - 1 ].created_at ),
   165     }
   114             scaleY = 600 / ( dtfintl - dtdebtl );
   166     for (var i in tweetData.posToDisplay) {
   115         timeWindow = paper.rect( 0, scaleY * ( dtfintl - dtfintw ), 600, scaleY * ( dtfintw - dtdebtw )).attr({"stroke":"#000080","fill":"#8080ff","fill-opacity":.2});
   167         var ds = displaySplitting[tweetData.followLast ? 1 : 0];
   116     }
   168         for (var j in ds.positions) {
       
   169             if (ds.positions[j] > i) {
       
   170                 var className = ds.classNames[j];
       
   171                 break;
       
   172             }
       
   173         }
       
   174         html += ( tweetData.posToDisplay[i] != -1 ? tweetToHtml(tweetByPos(tweetData.posToDisplay[i]), className) : placeHolder(className) );
       
   175     }
       
   176     $("#tweetlist").html(html);
       
   177     if (tweetData.followLast) {
       
   178         $("#tweet_" + tweetData.position).fadeIn(500);
       
   179     }
       
   180     drawTimeWindow();
       
   181 }
       
   182 
       
   183 function drawTimeWindow() {
       
   184     twPaper.clear();
       
   185     if (!tweetData.timeline || !tweetData.timeline.length) return;
       
   186     
       
   187     var dtfintl = new Date ( tweetData.timeline[ tweetData.timeline.length - 1 ].end ),
       
   188         dtdebtl = new Date ( tweetData.timeline[0].start ),
       
   189         dtfintw = new Date( tweetByPos( tweetData.end ).created_at ),
       
   190         dtdebtw = new Date( tweetByPos( tweetData.start ).created_at ),
       
   191         scY = 600 / ( dtfintl - dtdebtl );
       
   192     twPaper.rect( 0, scY * ( dtfintl - dtfintw ), 300, scY * ( dtfintw - dtdebtw )).attr({"stroke":"none","fill":"#8080ff","fill-opacity":.2});
       
   193     var dtcour = new Date( tweetByPos( tweetData.position ).created_at ),
       
   194         posY = scY * ( dtfintl - dtcour ); 
       
   195     twPaper.path("M0 "+posY+"L300 "+posY).attr({"stroke":"#ff0"});
       
   196 }
       
   197 
       
   198 function drawTimeLine() {
       
   199     tlPaper.clear();
       
   200     if (!tweetData.timeline || !tweetData.timeline.length) return;
       
   201     tweetData.tlTweetRects = [];
       
   202     var scaleY = 600 / tweetData.timeline.length,
       
   203         max = 0;
       
   204     for (var i = 0; i < tweetData.timeline.length; i++) {
       
   205         max = Math.max(max, tweetData.timeline[i].tweets);
       
   206     }
       
   207     var scaleX = 160 / max;
       
   208     
       
   209     // dessin de l'axe vertical
       
   210     
       
   211     tlPaper.path("M160 0L160 600").attr({"stroke":"#ccc"});
       
   212     
       
   213     // dessin de la date de début
       
   214     
       
   215     tlPaper.text(165, 592, new Date(tweetData.timeline[0].start).toLocaleTimeString()).attr({ "text-anchor" : "start", "font-size": "12px" });
       
   216     
       
   217     // dessin de la date de fin
       
   218     
       
   219     tlPaper.text(165, 7, new Date(tweetData.timeline[tweetData.timeline.length - 1].end).toLocaleTimeString()).attr({ "text-anchor" : "start", "font-size": "12px" });
       
   220     for (var i = 0; i < tweetData.timeline.length; i++) {
       
   221         var posY = 600 - (i * scaleY);
       
   222         
       
   223         // Si on est à une demi-heure, on trace un axe secondaire + heure
       
   224         
       
   225         if (i && !(new Date(tweetData.timeline[i].start).valueOf() % 1800000)) {
       
   226             tlPaper.path("M0 "+posY+"L165 "+posY).attr({"stroke":"#ccc"});
       
   227             tlPaper.text(165, posY, new Date(tweetData.timeline[i].start).toLocaleTimeString()).attr({ "text-anchor" : "start", "font-size": "12px" });
       
   228         }
       
   229         var anz = {
       
   230             "default" :tweetData.timeline[i].tweets
       
   231         };
       
   232         for (var j in tweetData.timeline[i].annotations) {
       
   233             anz.default -= tweetData.timeline[i].annotations[j];
       
   234             anz[j] = tweetData.timeline[i].annotations[j];
       
   235         }
       
   236         var posX = 0;
       
   237         for (var j in anz) {
       
   238             var largX = scaleX * anz[j];
       
   239             tlPaper.rect(posX, 600 - scaleY * (i+1), largX, scaleY).attr({"stroke": "none", "fill": annotations[j].colors.timeline});
       
   240             posX += largX;
       
   241         }
       
   242     }
       
   243     
       
   244     drawTimeWindow();
       
   245 /*    for (var i = 0; i < tweetData.arcs.length; i++) {
       
   246         var x1 = scaleX * (tmptw[tweetData.arcs[i].from].x + .5),
       
   247             x2 = scaleX * (tmptw[tweetData.arcs[i].to].x + .5),
       
   248             y1 = 600 - scaleY * (tmptw[tweetData.arcs[i].from].y + .5),
       
   249             y2 = 600 - scaleY * (tmptw[tweetData.arcs[i].to].y + .5),
       
   250             d = "M"+x1+" "+y1+"C";
       
   251         if (y1 == y2) {
       
   252             d += x1+" "+(y1 - 30)+" "+x2+" "+(y2 - 30);
       
   253         } else {
       
   254             d += (x1 + 60)+" "+y1+" "+(x2 + 60)+" "+y2;
       
   255         }
       
   256         paper.path(d+" "+x2+" "+y2);
       
   257     } */
   117 }
   258 }
   118 
   259 
   119 $(document).ready(function() {
   260 $(document).ready(function() {
   120     paper = Raphael("timeline", 160, 600);
   261     tlPaper = Raphael("timeline", 220, 600);
   121     socket = io.connect('http://' + S_IO_HOST + ':' + S_IO_PORT );
   262     twPaper = Raphael("timewindow", 220, 600);
       
   263     socket = io.connect('http://' + document.location.hostname );
   122     socket.on('tweetSummary', function (data) {
   264     socket.on('tweetSummary', function (data) {
   123         if (tweetData.position == tweetData.tweetcount - 1) {
   265         if (tweetData.tweetcount != data.tweetcount) {
   124             tweetData.position = data.tweetcount - 1;
   266             tweetData.tweetcount = data.tweetcount;
   125             displayTweets();
   267             tweetData.tlChanged = true;
   126             getTimeline();
   268             if (tweetData.followLast) {
   127         }
   269                 tweetData.position = data.tweetcount;
   128         tweetData.tweetcount = data.tweetcount;
   270             }
       
   271             updateDisplay();
       
   272         }
   129     });
   273     });
   130     socket.on('tweets', function (data) {
   274     socket.on('tweets', function (data) {
   131         switch (data.callback) {
   275         for (var i in data) {
   132             case "display":
   276             if (tweetData.posIndex.indexOf(data[i].pos) == -1) {
   133                 tweetData.tweetsOnDisplay = data.tweets;
   277                 tweetData.tweets.push(data[i]);
   134                 tweetData.tweetsOnDisplay.reverse();
   278                 tweetData.posIndex.push(data[i].pos);
   135                 html = '';
   279             }
   136                 for (var i in tweetData.tweetsOnDisplay) {
   280         }
   137                     html += tweetToHtml(tweetData.tweetsOnDisplay[i]);
   281         drawTweetList();
   138                 }
       
   139                 $("#tweetlist").html(html);
       
   140                 showTimeWindow();
       
   141                 break;
       
   142         }
       
   143     });
   282     });
   144     socket.on('timeline', function (data) {
   283     socket.on('timeline', function (data) {
   145         tweetData.timelineOnDisplay = data.timeline;
   284         tweetData.timeline = data;
   146         paper.clear();
   285         drawTimeLine();
   147         timeWindow = null;
   286     });
   148         var max = 0;
   287     socket.on('display', function (data) {
   149         for (var i in data.timeline) {
   288         tweetData.tlChanged = false;
   150             max = Math.max(max, data.timeline[i].tweets.length);
   289         for (var i in data.tweets) {
   151         }
   290             if (tweetData.posIndex.indexOf(data.tweets[i].pos) == -1) {
   152         var scaleX = 160 / max,
   291                 tweetData.tweets.push(data.tweets[i]);
   153             scaleY = 600 / data.timeline.length,
   292                 tweetData.posIndex.push(data.tweets[i].pos);
   154             tmptw = [];
   293             }
   155         for (var i = 0; i < tweetData.tweetcount; i++) {
   294         }
   156             tmptw.push({});
   295         if (data.timeline) {
   157         }
   296             tweetData.timeline = data.timeline
   158         for (var i = 0; i < data.timeline.length; i++) {
   297         }
   159             for (var j = 0; j < data.timeline[i].tweets.length; j++) {
   298         tweetData.arcs = data.arcs;
   160                 var coul = "#585858";
   299         drawDisplay();
   161                 for (var k in data.timeline[i].annotations) {
   300     });
   162                     if (data.timeline[i].annotations[k].indexOf(data.timeline[i].tweets[j]) != -1) {
   301     $("#tweetlist").mousewheel(function(e, d) {
   163                         coul = colors[k];
   302         wheeldelta += d;
   164                     }
   303         if (Math.abs(wheeldelta) >= 1) {
   165                 }
   304             tweetData.position = Math.min( tweetData.tweetcount, Math.max(1, parseInt(wheeldelta) + tweetData.position ) );
   166                 tmptw[data.timeline[i].tweets[j]].y = i;
   305             tweetData.followLast = (tweetData.position == tweetData.tweetcount);
   167                 tmptw[data.timeline[i].tweets[j]].x = j;
   306             updateDisplay();
   168                 paper.rect(scaleX * j + (scaleX > 3 ? .5 : 0), 600 - ((i+1) * scaleY) + (scaleY > 3 ? .5 : 0), scaleX - (scaleX > 3 ? 1 : 0), scaleY - (scaleY > 3 ? 1 : 0)).attr({"stroke":"none","fill":coul});
   307             wheeldelta = 0;
   169             }
   308         }
   170         }
   309         return false;
   171         for (var i = 0; i < data.arcs.length; i++) {
   310     });
   172             var x1 = scaleX * (tmptw[data.arcs[i].from].x + .5),
   311     $("#timewindow").mousewheel(function(e, d) {
   173                 x2 = scaleX * (tmptw[data.arcs[i].to].x + .5),
   312         wheeldelta += d;
   174                 y1 = 600 - scaleY * (tmptw[data.arcs[i].from].y + .5),
   313         if (Math.abs(wheeldelta) >= 1) {
   175                 y2 = 600 - scaleY * (tmptw[data.arcs[i].to].y + .5),
   314             if (wheeldelta > 0) {
   176                 d = "M"+x1+" "+y1+"C";
   315                 setTimeZoom(tweetData.timeLevel + 1);
   177             if (y1 == y2) {
       
   178                 d += x1+" "+(y1 - 60)+" "+x2+" "+(y2 - 60);
       
   179             } else {
   316             } else {
   180                 d += (x1 + 60)+" "+y1+" "+(x2 + 60)+" "+y2;
   317                 setTimeZoom(tweetData.timeLevel - 1);
   181             }
   318             }
   182             paper.path(d+" "+x2+" "+y2);
   319             wheeldelta = 0;
   183         }
   320         }
   184         showTimeWindow();
   321         return false;
   185     });
   322     });
   186 });
   323 });