js/playscreen.js
changeset 5 490e4d1b6fee
child 6 14dd1980b0b9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/playscreen.js	Wed Mar 06 18:09:40 2013 +0100
@@ -0,0 +1,689 @@
+var topicPoubelle = 13;
+
+var adjust = 54;
+
+var deltaT = new Date("Wed, 02 May 2012 19:00:00 +0000") / 1000 + adjust;
+
+function solrUrl(table, params) {
+    return "http://159.217.144.101:8050/sia-solr/" + table + "/select?" + $.param(params) + "&wt=json&json.wrf=?";
+}
+
+function showData() {
+    
+    var topicHash = document.location.hash || "#topics=5,15";
+    
+    var ordertag = 0;
+
+    function secsToString(seconds) {
+        var hours = Math.floor(seconds/3600),
+            minutes = Math.floor(seconds/60) % 60,
+            secs = Math.floor(seconds % 60);
+        function pad(n) {
+            var r = n.toString();
+            while (r.length < 2) {
+                r = "0" + r;
+            }
+            return r;
+        }
+        return (hours ? (hours + ":") : "") + pad(minutes) + ":" + pad(secs);
+    }
+    
+    $(".duration").text(secsToString(data.duration));
+        
+    var nmmso = data.segments.length;
+    
+    data.topics.forEach(function(topic) {
+        topic.score = 0;
+        topic.weights = [];
+        for (var i = 0; i < nmmso; i++) {
+            topic.weights.push(0);
+        }
+    });
+    
+    data.segments.forEach(function(mmso, i) {
+        mmso.topics.forEach(function(t) {
+            data.topics[t.topic].weights[i] = t.weight;
+            data.topics[t.topic].score += t.weight;
+        });
+    });
+    
+    var sortedTopics = data.topics.filter(function(t) {
+        return t.index !== topicPoubelle;
+    }).sort(function(a,b) {
+        return b.score - a.score;
+    });
+    
+    
+    function showTopics(topiclist) {
+        var tbhtml = topiclist.reduce(function(mem, topic) {
+            var wordsToShow = topic.words.slice(0,3),
+                max = wordsToShow[0].weight,
+                min = Math.min(wordsToShow[wordsToShow.length - 1].weight, max - .01),
+                scale = 8 / (max - min);
+            var li = '<li class="shadow-block topic" data-topic-id="'
+                + topic.index
+                + '" data-timestamp="999999"><ul class="topic-words">'
+                + wordsToShow.reduce(function(memwords, word) {
+                    return memwords
+                        + '<li style="font-size: '
+                        + ( 8 + scale * (word.weight - min) )
+                        + 'px;">'
+                        + word.word
+                        + '</li>';
+                },"")
+                + '</ul></li>';
+            return mem + li;
+        },'');
+        var tb = $(".topics-block");
+        tb.html(tbhtml);
+        tb.css("top",0);
+        
+        showTopicViz();
+    }
+    
+    var tweetLines = [];
+    
+    function showTopicViz() {
+        var selectedBlocks = $(".topic.selected, .topic.hover"),
+            sbl = selectedBlocks.length,
+            topicBlocks = $(".topic");
+        if (!sbl && topicBlocks.length < sortedTopics.length) {
+            selectedBlocks = topicBlocks;
+            sbl = selectedBlocks.length;
+        }
+        var topicsAndColors = [],
+        	colors = {};
+        selectedBlocks.each(function() {
+            var el = $(this),
+                topicid = parseInt(el.attr("data-topic-id"));
+            topicsAndColors.push({
+                "$": el,
+                timestamp: parseInt(el.attr("data-timestamp")),
+                hovered: el.hasClass("hover"),
+                id: topicid,
+                topic: data.topics[topicid]
+            });
+        });
+        topicsAndColors.sort(function(a,b) {
+           return ( a.timestamp - b.timestamp ) || ( a.id - b.id );
+        });
+        topicBlocks.css("background","");
+        topicsAndColors.forEach(function(topic, i) {
+            topic.color = topic.hovered ? "#ffff00" : colorset[i % colorset.length];
+            colors[topic.id] = topic.color;
+            topic.$.css("background", topic.color);
+        });
+        
+        for (var i = 0; i < nmmso; i++) {
+            var opacity = 0,
+                rgb = [0,0,0];
+            topicsAndColors.forEach(function(topic) {
+                var c = Raphael.getRGB(topic.color),
+                    o = topic.topic.weights[i];
+                rgb[0] += c.r * o;
+                rgb[1] += c.g * o;
+                rgb[2] += c.b * o;
+                opacity += o;
+            });
+            if (opacity) {
+                color = Raphael.rgb.apply(Raphael, rgb.map(function(c) {
+                    return c/opacity;
+                }));
+                var attr = {
+                    fill: color,
+                    opacity: .5 + .5 * opacity
+                };
+                segmentrects[i].show();
+                segmentrects[i].attr(attr);
+                if (i >= localMmsoDelta && i < localMmsoDelta + localMmsos.length) {
+                	localMmsos[i - localMmsoDelta].show();
+                	localMmsos[i - localMmsoDelta].attr(attr);
+                }
+            } else {
+                segmentrects[i].hide();
+                if (i >= localMmsoDelta && i < localMmsoDelta + localMmsos.length) {
+                	localMmsos[i - localMmsoDelta].hide();
+                }
+            }
+        }
+        
+        tweetLines.forEach(function(tl) { tl.remove(); });
+        tweetLines = [];
+        
+        var deltaY = $(".play-dataviz").offset().top;
+        
+        $(".play-localtweets .tweet").each(function() {
+        	var el = $(this),
+        		liY = + el.offset().top + el.outerHeight() / 2 - deltaY,
+        		tY = localyscale * (+el.attr("data-timestamp") - localpos + localduration / 2),
+        		p = "M" + localL + "," + tY + "L" + localR + "," + tY + "L320," + liY,
+        		path = paper.path(p);
+    		$(this).css("background",colors[el.attr("data-topic-id")] || "");
+    		path.attr({
+    			stroke: "#ccc"
+    		});
+    		tweetLines.push(path);
+        });
+        throttledGetTweets();
+        
+    }
+    
+    var jqsvg = $(".play-svg"),
+        paper = new Raphael(jqsvg[0]),
+        totalR = jqsvg.width(),
+        ph = jqsvg.height(),
+        globW = 85,
+        globL = 40,
+        localL = 155,
+        localW = 85,
+        localR = (localL + localW),
+        localTimeR = (localL + localW) + globL,
+        globR = (globW + globL),
+        yscale = ph / data.duration,
+        mx = Math.max.apply(Math, data.minutes.map(function(s) { return s.count})),
+        xscale = globW/mx;
+    
+    var segmentrects = data.segments.map(function(mmso) {
+        var rect = paper.rect(globL, yscale * mmso.start, globW, yscale * mmso.duration);
+        rect.attr({stroke: "none"});
+        return rect;
+    });
+    
+    var d = "M" + data.minutes.map(function(s) {
+            var x = globL + xscale * s.count;
+            return x
+                + ","
+                + yscale * (s.from + 20)
+                + "L"
+                + x
+                + ","
+                + yscale * (s.from + 40);
+        }).join("L");
+        
+    paper.path(d).attr({
+        "stroke-width": 4,
+        "stroke": "#000000",
+        opacity: .5
+    });
+    paper.path(d).attr({
+        "stroke-width": 1,
+        "stroke": "#ffffff"
+    });
+    
+    for (var i=0; i < data.duration; i += 1800) {
+        var y = yscale * i;
+        paper.path("M0" + "," + y + "l" + globR + ",0").attr({stroke: "#666"});
+        paper.text(0, y + 6, secsToString(i)).attr({
+        	"text-anchor": "start",
+            "fill": "#ffffff"
+        });
+    }
+    paper.text(0, ph-8, secsToString(data.duration)).attr({
+        "text-anchor": "start",
+        "fill": "#ffffff"
+    });
+    paper.path("M0" + globR + ",0L" + localTimeR + ",0" ).attr({stroke: "#666"});
+    paper.path("M0," + (ph-1) + "l" + localTimeR + ",0" ).attr({stroke: "#666"});
+    
+    paper.path("M" + globR + ",0l0," + ph).attr({stroke: "#666"});
+    paper.path("M" + localL + ",0l0," + ph).attr({stroke: "#666"});
+    
+    var entonnoir = paper.path("").attr("fill","#333"),
+    	localStartText = paper.text(localTimeR,6,"").attr({
+    		"text-anchor": "end",
+    		"fill": "#ffffff"
+    	}),
+    	localEndText = paper.text(localTimeR,ph - 8, "").attr({
+    		"text-anchor": "end",
+    		"fill": "#ffffff"
+    	}),
+    	localTimes = [],
+    	localMmsos = [],
+    	localMmsoDelta,
+    	mmsoAlt = [],
+    	lowerFiveSecs,
+    	higherFiveSecs,
+    	localyscale;
+	
+	entonnoir.toBack();
+    
+    function showLocal() {
+		localyscale = ph / localduration;
+    	var localstart = localpos - localduration/2;
+    		localend = localpos + localduration/2;
+    		globtop = yscale * localstart,
+    		globbottom = yscale * localend,
+    		betweenx = (globR + localL) / 2,
+    		betweenyt = (globtop) / 2,
+    		betweenyb = (globbottom + ph) / 2,
+    		curve = (localL - globR) / 2,
+    		entonnoird = "M0," + globtop + "l" + globR + ",0Q" + betweenx + "," + globtop + "," + betweenx + ","
+    			+ Math.max(globtop - curve, betweenyt) + "L" + betweenx + "," + Math.min(curve, betweenyt) + "Q"
+    			+ betweenx + ",0," + localL + ",0"
+    			+ "L" + localR + ",0L" + localR + "," + ph + "L" + localL + "," + ph + "Q" + betweenx + "," + ph + ","
+    			+ betweenx + "," + Math.max(ph - curve, betweenyb) +"L" + betweenx + "," + Math.min(globbottom + curve, betweenyb)
+    			+ "Q" + betweenx + "," + globbottom + "," + globR + "," + globbottom + "L0," + globbottom;
+			
+		entonnoir.attr("path",entonnoird);
+		localTimes.forEach(function(t) {
+			t.text.remove();
+			t.line.remove();
+		});
+		localMmsos.forEach(function(t) {
+			t.remove();
+		});
+		mmsoAlt.forEach(function(t) {
+			t.remove();
+		});
+		var filteredSegments = data.segments.filter(function(s) {
+			return s.start < localend && s.end > localstart;
+		});
+		localMmsoDelta = parseInt(filteredSegments[0].id.split("_")[1]);
+		localMmsos = filteredSegments.map(function(s) {
+			var y = localyscale * (s.start - localstart),
+				h = localyscale * s.duration;
+	        var rect = paper.rect( localL, y, localW, h );
+	        rect.attr({stroke: "none", title: s.id});
+	        if (parseInt(s.id.replace("MMSO_","")) % 2) {
+	        	var altrect = paper.rect( localR, y, (totalR - localR), h);
+	        	altrect.attr({stroke: "none", fill: "#222"});
+	        	mmsoAlt.push(altrect);
+	        }
+	        return rect;
+		});
+		localStartText.attr("text", secsToString(localstart)).toFront();
+		localEndText.attr("text", secsToString(localend)).toFront();
+		for ( var i = (1 + Math.floor(localstart / 120)) * 120; i < localend; i += 120 ) {
+			var y = localyscale*(i - localstart)
+			localTimes.push({
+				text: paper.text(localTimeR,6+y,secsToString(i)).attr({
+		    		"text-anchor": "end",
+		    		"fill": "#ffffff"
+		    	}),
+		    	line: paper.path("M0" + localL + "," + y + "L" + localTimeR + "," + y).attr({stroke: "#666"})
+			});
+		}
+		if (lowerFiveSecs) {
+			lowerFiveSecs.remove();
+		}
+		if (higherFiveSecs) {
+			higherFiveSecs.remove();
+		}
+		var filteredFiveSecs = data.fiveseconds.slice(Math.floor(localstart / 5), Math.ceil(localend / 5));
+        var counts = filteredFiveSecs.map(function(s) { return s.count}),
+        	lmx = Math.max.apply(Math, counts),
+        	lmi = Math.min.apply(Math, counts.concat([lmx - 1]))
+        	lxscale = localW/(lmx-lmi);
+       	
+	    var d = "M" + filteredFiveSecs.map(function(s) {
+	            var x = localL + lxscale * (s.count - lmi);
+	            return x
+	                + ","
+	                + localyscale * (s.from + 1 - localstart)
+	                + "L"
+	                + x
+	                + ","
+	                + localyscale * (s.from + 4 - localstart);
+	        }).join("L");
+	        
+	    lowerFiveSecs = paper.path(d).attr({
+	        "stroke-width": 4,
+	        "stroke": "#000000",
+	        opacity: .5
+	    });
+	    higherFiveSecs = paper.path(d).attr({
+	        "stroke-width": 1,
+	        "stroke": "#ffffff"
+	    });
+		showTopicViz();
+    }
+    
+    
+    var lastPos, lastDuration, lastTopics;
+    
+    var tweetTemplate = _.template('<li class="tweet" data-timestamp="<%= _timestamp %>" data-topic-id="<%= _topic %>"><img src="<%- profile_image_url %>" /><p>@<%- from_user_name %>: <%- text %></p></li>'),
+    	callnum = 0;
+    
+    function getTweets() {
+    	var topicIds = Array.prototype.join.call($(".topic.selected").map(function(){return $(this).attr("data-topic-id")}));
+    	if (localduration === lastDuration && lastTopics === topicIds && Math.abs(localpos - lastPos) < (localduration / 10)) {
+    		console.log("We already have these tweets, delta =", localpos - lastPos );
+    		return;
+    	}
+    	if (!topicIds) {
+    		console.log("No topics selected");
+    		return;
+    	}
+    	var localcall = ++callnum;
+    	console.log("getTweets was called",localcall,(function(d){return d.toLocaleTimeString()+"."+d.getMilliseconds()})(new Date()));
+    	var tweetids = {},
+    		rqtodo = 0,
+    		rqdone = 0,
+    		topics = topicIds.split(",");
+    	
+    	function getMmsoTopicTweets(mmsoid, topic, ntweets) {
+    		rqtodo++;
+			TopicsBean.bestSocialInteractionsIdsMatching(mmsoid, topic, 0, ntweets, {
+				callback: function(tw) {
+					for (var k = 0; k < tw.length; k++) {
+						tweetids[tw[k]] = topic;
+					}
+					rqdone++;
+					if (rqdone === rqtodo) {
+						loadTweets();
+					}
+				}
+			});
+    	}
+    	
+    	function loadTweets() {
+    		console.log("Tweet IDs retrieved",localcall,(function(d){return d.toLocaleTimeString()+"."+d.getMilliseconds()})(new Date()));
+    		var u = _.keys(tweetids);
+			$.getJSON(
+				solrUrl(
+					"twitter",
+					{
+						q:"id:(" + u.join(" OR ") + ")",
+						rows: u.length
+					}
+				),
+				function(t) {
+    				console.log("Full tweets retrieved",localcall,(function(d){return d.toLocaleTimeString()+"."+d.getMilliseconds()})(new Date()));
+					var tweets = t.response.docs;
+					var s = 600 / (1+tweets.length);
+					tweets.forEach(function(tweet, i) {
+						tweet._date = new Date(tweet.created_at);
+						tweet._timestamp = tweet._date.valueOf() / 1000 - deltaT;
+						tweet._topic = tweetids[tweet.id_str]
+					});
+					tweets.sort(function(a,b) {
+						return a._date - b._date;
+					});
+					var html = tweets.reduce(function(mem, tweet) {
+						return mem + tweetTemplate(tweet);
+					},'');
+					$(".play-localtweets").html(html);
+					var h = 0;
+					$(".play-localtweets .tweet").each(function() {
+						h += $(this).outerHeight();
+					});
+					$(".play-localtweets .tweet").css("margin-top",Math.max(0,($(".play-bottom").height() - h)/(tweets.length+1)));
+					showTopicViz();
+				}
+			);
+			$(".play-localtweets").html("");
+			showTopicViz();
+    	}
+    	
+    	function getMmsoTweets(nmmso) {
+    		var mmso = data.segments[nmmso],
+    			mmsopixels = localyscale * (Math.min(localpos + localduration / 2, mmso.end) - Math.max(localpos - localduration / 2, mmso.start));
+    		var ntweets = Math.floor(( 50 + mmsopixels ) / 80),
+    			mmsotopics = [];
+			if (!ntweets) {
+				return;
+			}
+			for (var j = 0; j < topics.length; j++) {
+				var weight = data.topics[topics[j]].weights[nmmso];
+				if (weight > .05) {
+					mmsotopics.push({topic:parseInt(topics[j]),weight:weight,ntweets:0});
+				}
+			}
+			mmsotopics.sort(function(a,b){return b.weight - a.weight});
+			if (mmsotopics.length) {
+				for (var j = 0; j < ntweets; j++) {
+					mmsotopics[j % mmsotopics.length].ntweets++;
+				}
+				mmsotopics = mmsotopics.filter(function(t) { return !!t.ntweets });
+				for (var j = 0; j < mmsotopics.length; j++) {
+					getMmsoTopicTweets(mmso.id, mmsotopics[j].topic, mmsotopics[j].ntweets);
+				}
+			} else {
+				var t = [], m = {};
+				while (t.length < ntweets) {
+					t = t.concat(topics);
+				}
+				for (var j = 0; j < ntweets; j++) {
+					m[t[j]] = 1 + (m[t[j]]||0);
+				}
+				_(m).each(function(v,k) {
+					getMmsoTopicTweets(mmso.id, k, v);
+				});
+			}
+    	}
+    	
+    	dwr.engine.beginBatch();
+    	for (var i = 0; i < localMmsos.length; i++) {
+    		getMmsoTweets(localMmsoDelta + i);
+    	}
+    	dwr.engine.endBatch();
+    	lastPos = localpos;
+    	lastDuration = localduration;
+    	lastTopics = topicIds;
+    }
+    
+    var throttledGetTweets = _.throttle(_.debounce(getTweets, 500), 10000),
+    	throttledShowLocal = _.throttle(showLocal, 100);
+    
+    showTopics(sortedTopics);
+    (topicHash.match(/\d+/g) || []).forEach(function(id) {
+    	$(".topic[data-topic-id=" + id + "]").addClass("selected").attr("data-timestamp",++ordertag);
+    });
+    
+    var localpos = 7100,
+    	localduration = 600;
+    	
+    showLocal();
+    
+    $(".topics-block").on("mouseenter", ".topic", function() {
+        var el = $(this);
+        el.addClass("hover");
+        showTopicViz();
+    }).on("mouseleave", ".topic", function() {
+        $(this).removeClass("hover");
+        showTopicViz();
+    }).on("click", ".topic", function() {
+        var el = $(this);
+        $(this).toggleClass("selected");
+        el.attr("data-timestamp", el.hasClass("selected") ? ++ordertag : 999999);
+        showTopicViz();
+    });
+    
+    var mouseIsDown, isDragging, startY, startT, startPos, scrollGlobal, speedscale, slowiterations;
+    
+    function inertia() {
+    	startPos = localpos;
+    	window.setTimeout(function() {
+    		speedscale = .75 * speedscale;
+    		localstart += 100*speedscale;
+    		throttledShowLocal();
+    		if (slowiterations < 5) {
+    			inertia();
+    		}
+    		slowiterations++;
+    	}, 100);
+    }
+    
+    $("body").mouseup(function() { mouseIsDown = false; });
+    
+    $(".play-dataviz").mousedown(function(e) {
+    	var l = $(this).offset().left,
+    		scrollLimit = l + 280;
+		if (e.pageX < scrollLimit) {
+			mouseIsDown = true;
+	    	startY = e.pageY;
+	    	startT = new Date();
+	    	startPos = localpos;
+	    	scrollGlobal = e.pageX < (l + 140);
+	    	e.preventDefault();
+		}
+    }).mousemove(function(e) {
+    	if (mouseIsDown) {
+    		if (isDragging) {
+    			var limit = $(this).offset().left + 140,
+    				deltaY = e.pageY - startY,
+    				delta = Math.floor(deltaY / (scrollGlobal ? yscale : - localyscale));
+				localpos = Math.max(localduration / 2, Math.min(data.duration - localduration / 2, startPos + delta));
+				throttledShowLocal();
+    		} else {
+    			isDragging = true;
+    		}
+    	}
+    }).mouseup(function(e) {
+    	if (scrollGlobal && !isDragging) {
+    		
+    		var posY = e.pageY - $(this).offset().top;
+    		localpos = Math.max(localduration / 2, Math.min(data.duration - localduration / 2, Math.floor(posY / yscale)));
+			throttledShowLocal();
+    	}
+/*    	if (isDragging) {
+			var limit = $(this).offset().left + 140,
+				deltaY = e.pageY - startY,
+				deltaT = new Date() - startT,
+				delta = Math.floor(deltaY / (scrollGlobal ? yscale : - localyscale));
+			speedscale = delta / deltaT;
+			slowiterations = 0;
+			inertia();
+    } */
+    });
+    
+    var totalScroll = 0, zoomlevels = [ 1800, 900, 600, 300, 120, 60 ], currentlevel = 2;
+    
+    $(".play-dataviz").mousewheel(function(_event, _scrolldelta) {
+    	totalScroll += _scrolldelta;
+	    if (Math.abs(totalScroll) >= 1) {
+	    	var d = (totalScroll > 0 ? 1 : -1),
+	    		newlevel = Math.max(0, Math.min(zoomlevels.length - 1, currentlevel + d));
+    		if (newlevel !== currentlevel) {
+    			currentlevel = newlevel;
+    			localduration = zoomlevels[currentlevel];
+    			localpos = Math.max(localduration / 2, Math.min(data.duration - localduration / 2, localpos));
+    			throttledShowLocal();
+    		}
+	        totalScroll = 0;
+	    }
+    })
+    
+}
+
+var data = { duration: 10200, topics: [] },
+    colorset = ["#E41A1C", "#377EB8", "#4DAF4A", "#984EA3", "#FF7F00", "#A65628", "#F781BF"];
+
+$(function() {
+	
+	dwr.engine.setErrorHandler(function(a, b) { console.error("DWR", b); });
+    
+    $(".topics-block").draggable({axis:"x"});
+    
+    var loadedSteps = 0,
+        stepsToFullyLoaded = 0;
+    
+    function checkIfLoaded() {
+        loadedSteps++;
+        if (loadedSteps >= stepsToFullyLoaded) {
+            setTimeout(showData,0);
+        }
+    }
+    
+    function loadJson(url, propname, callback) {
+        stepsToFullyLoaded++;
+        $.getJSON(url, function(d) {
+            if (callback) {
+                var res = callback(d);
+            } else {
+                var res = d;
+            }
+            if (propname) {
+                data[propname] = res;
+            }
+            checkIfLoaded();
+        });
+    }
+    
+    function loadFromTopicsBean(method, propname, args, callback) {
+        stepsToFullyLoaded++;
+        var arg = args || [],
+            cb = function(d) {
+                if (callback) {
+                    var res = callback(d);
+                } else {
+                    var res = d;
+                }
+                if (propname) {
+                    data[propname] = res;
+                }
+                checkIfLoaded();
+            }
+        arg.push({callback: cb});
+        TopicsBean[method].apply(TopicsBean,arg);
+    }
+    
+    loadJson("data/minutes.json", "minutes");
+    loadJson("data/5secs.json", "fiveseconds");
+    
+    loadJson(
+        solrUrl("MMSO", {q: "*:*", fl: "topic*,MMSO_id,multimediaSegment", rows: 250 }),
+        "segments",
+        function(d) {
+            return d.response.docs.map(function(mmso) {
+                var tc = mmso.multimediaSegment.match(/\d+/g),
+                    start = parseInt(tc[0]),
+                    end = parseInt(tc[1]),
+                    topics = [];
+                for (var k in mmso) {
+                    if (k.substr(0,5) === "topic" && mmso[k] > .01) {
+                        topics.push({
+                            topic: parseInt(k.substr(5)),
+                            weight: mmso[k]
+                        })
+                    }
+                }
+                topics.sort(function(a,b) {
+                    return b.weight - a.weight;
+                });
+/*                topics = topics.filter(function(t) {
+                    return t.topic !== topicPoubelle
+                }).slice(0,1);
+                topics[0].weight = 1; */
+                return {
+                    id: mmso.MMSO_id,
+                    start: start,
+                    end: end,
+                    duration: end - start,
+                    topics: topics
+                }
+            }).sort(function(a,b) {
+                return a.start - b.start;
+            });
+        })
+    
+    dwr.engine.setTimeout(60000);
+    TopicsBean._path = "http://159.217.144.101:8050/sia-solr/dwr";
+    
+    loadFromTopicsBean("getTopicsNumber",false,false,function(topic_count) {
+        for (var i = 0; i < topic_count; i++) {
+            data.topics.push(null);
+        }
+        dwr.engine.beginBatch();
+        data.topics.forEach(function(v, k) {
+            loadFromTopicsBean("getTopicDistribution",false,[k, 50, false],function(topic) {
+                var words = topic.match(/[^=,{]+=0.\d{0,8}/g);
+                data.topics[k] = {
+                    index: k,
+                    words: words.map(function(w) {
+                        var parts = w.split("=");
+                        return {
+                            word: parts[0].trim(),
+                            weight: parseFloat(parts[1])
+                        }
+                    })
+                }
+            });
+            
+        });
+        dwr.engine.endBatch();
+        
+    });
+    
+    
+});