Changed tweets, added eca
This commit is contained in:
		
							
								
								
									
										368
									
								
								template_static/lib/charts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										368
									
								
								template_static/lib/charts.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,368 @@ | ||||
| (function($, block) { | ||||
|  | ||||
| // a simple rolling chart with memory | ||||
| block.fn.rolling_chart = function(config) { | ||||
|     // combine default configuration with user configuration | ||||
|     var options = $.extend({ | ||||
|         memory: 100, | ||||
| 	// required!! | ||||
|         series: { | ||||
|             'default': {data: []} | ||||
|         }, | ||||
| 	// flot initialization options | ||||
|         options: { | ||||
|             xaxis: { | ||||
|                 show: false | ||||
|             } | ||||
|         } | ||||
|     }, config); | ||||
|  | ||||
|     // maintain state for this block | ||||
|     var data = {}; | ||||
|     for(var k in options.series) { | ||||
|         data[k] = (options.series[k].data || []).slice(); | ||||
|     } | ||||
|  | ||||
|     // function to project our state to something the library understands | ||||
|     var prepare_data = function() { | ||||
|         var result = []; | ||||
|  | ||||
|         // process each series | ||||
|         for(var k in data) { | ||||
|             var series = data[k]; | ||||
|             var points = []; | ||||
|  | ||||
|             // create point pairs and gap values | ||||
|             for(var i in series) { | ||||
|                 if(series[i] == null) { | ||||
|                     points.push(null); | ||||
|                 } else { | ||||
|                     points.push([i, series[i]]); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // combine state data with series configuration by user | ||||
|             result.push($.extend(options.series[k], {data: points})); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     }; | ||||
|  | ||||
|     // initial setup of library state (also builds necessary HTML) | ||||
|     var plot = $.plot(this.$element, prepare_data(), options.options); | ||||
|  | ||||
|  | ||||
|     // register actions for this block | ||||
|     this.actions({ | ||||
|         'add': function(e, message) { | ||||
|             // if the 'value' field is used, update all series (useful with a single series) | ||||
|             if(typeof message.values == 'undefined' && typeof message.value != 'undefined') { | ||||
|                 message.values = {} | ||||
|                 for(var k in options.series) { | ||||
|                     message.values[k] = message.value; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // update all series | ||||
|             for(var k in options.series) { | ||||
|                 // roll memory | ||||
|                 if(data[k].length > options.memory) { | ||||
|                     data[k] = data[k].slice(1); | ||||
|                 } | ||||
|  | ||||
|                 // insert value or gap (in case of null) | ||||
|                 data[k].push(message.values[k]); | ||||
|             } | ||||
|  | ||||
|             // update HTML | ||||
|             plot.setData(prepare_data()); | ||||
|             plot.setupGrid(); | ||||
|             plot.draw(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // return element to allow further work | ||||
|     return this.$element; | ||||
| } | ||||
|  | ||||
| // | ||||
| // | ||||
| // | ||||
|  | ||||
| // a simple linechart example | ||||
| block.fn.linechart = function(config) { | ||||
|     var options = $.extend({ | ||||
| 	// required | ||||
|         series : {default:{}}, | ||||
| 	// flot initialization options | ||||
|         options : {} | ||||
|     }, config); | ||||
|  | ||||
|     // create empty linechart with parameter options | ||||
|     var plot = $.plot(this.$element, [],options.options); | ||||
|  | ||||
|     // dict containing the labels and values | ||||
|     var linedata_series = {}; | ||||
|     var linedata_first; | ||||
|  | ||||
|     var initline = function(series) { | ||||
| 	linedata_first = undefined; | ||||
| 	for(var k in series) { | ||||
| 	   var si = series[k]; | ||||
| 	   si.data = []; | ||||
| 	   linedata_series[k] = si; | ||||
| 	   if ( linedata_first == undefined ) | ||||
| 	       linedata_first = si; | ||||
| 	} | ||||
|     } | ||||
|  | ||||
|     initline(options.series); | ||||
|  | ||||
|     var addline = function(label, values) { | ||||
|     	var data; | ||||
|  | ||||
| 	if (linedata_series.hasOwnProperty(label)) | ||||
| 		data = linedata_series[label].data; | ||||
| 	else | ||||
| 		data = linedata_first.data; | ||||
| 	for(var v in values) { | ||||
| 		data.push(values[v]); | ||||
| 	} | ||||
| 	redraw(); | ||||
|     } | ||||
|  | ||||
|     var setline = function(label, values) { | ||||
| 	if (linedata_series.hasOwnProperty(label)) | ||||
| 		linedata_series[label].data = values; | ||||
| 	else | ||||
| 		linedata_first.data = values; | ||||
| 	redraw(); | ||||
|     } | ||||
|  | ||||
|     var redraw = function() { | ||||
|         var result = []; | ||||
|     	for(var k in linedata_series) { | ||||
| 	    if (linedata_series.hasOwnProperty(k)) { | ||||
| 	    	var line_serie = linedata_series[k]; | ||||
|  | ||||
|  		result.push({label:k,data:line_serie.data}); | ||||
| 	    } | ||||
| 	} | ||||
|         plot.setData(result); | ||||
| 	plot.setupGrid(); | ||||
|         plot.draw(); | ||||
|     } | ||||
|  | ||||
|     var reset = function() { | ||||
| 	initline(options.series); | ||||
|     } | ||||
|  | ||||
|     this.actions({ | ||||
|         'set': function(e, message) { | ||||
| 	    setline(message.series, message.value); | ||||
|         }, | ||||
|         'add': function(e, message) { | ||||
| 	    addline(message.series, message.value); | ||||
|         }, | ||||
|         'reset': function(e, message) { | ||||
| 	    reset(); | ||||
| 	} | ||||
|     }); | ||||
|     // return element to allow further work | ||||
|     return this.$element; | ||||
| } | ||||
|  | ||||
| // | ||||
| // | ||||
| // | ||||
|  | ||||
| // a simple barchart example | ||||
| block.fn.barchart = function(config) { | ||||
|     var options = $.extend({ | ||||
|         filter_function : function(category,val,max) { return true; }, | ||||
| 	// required | ||||
|     	series : { "default":{ | ||||
|         	data: {}, | ||||
|         	label: "default", | ||||
|         	bars: { | ||||
|                 	show: true, | ||||
|                 	barWidth: 0.2, | ||||
|                 	align: "left" | ||||
|             	} | ||||
|        	  | ||||
|     	} }, | ||||
| 	// flot initialization options | ||||
| 	options: { xaxis: { | ||||
|                 mode: "categories", | ||||
|                 tickLength: 0 | ||||
|             }} | ||||
|  | ||||
|     }, config); | ||||
|  | ||||
|     var bardata_series = options.series; | ||||
|     var bardata_first; | ||||
|  | ||||
|     for (bardata_first in bardata_series) break; | ||||
|  | ||||
|     var translate_bar = function() { | ||||
|         var result = []; | ||||
| 	for(var k in bardata_series) { | ||||
| 	    if (bardata_series.hasOwnProperty(k)) { | ||||
| 		var newserie = jQuery.extend({}, bardata_series[k]); | ||||
|         	var newdata = []; | ||||
| 		var data = newserie.data; | ||||
| 		var max = 0; | ||||
|  | ||||
| 		for(var l in data) { | ||||
| 	    	    if (data.hasOwnProperty(l)) { | ||||
|                         max = Math.max(max, data[l]); | ||||
| 		    } | ||||
| 		} | ||||
|  | ||||
| 		for(var l in data) { | ||||
| 	    	    if (data.hasOwnProperty(l)) { | ||||
| 		        if ( options.filter_function(l,data[l],max) ) | ||||
| 			    newdata.push([l,data[l]]); | ||||
| 		    } | ||||
| 		} | ||||
| 		newserie.data = newdata; | ||||
| 		result.push(newserie); | ||||
| 	    } | ||||
| 	} | ||||
| 	return result; | ||||
|     } | ||||
|  | ||||
|     var plot = $.plot(this.$element, translate_bar(), options.options); | ||||
|  | ||||
|     var addbar = function(serie_label, category, value) { | ||||
|     	var data; | ||||
|  | ||||
|     	if ( serie_label == undefined ) | ||||
| 		data = bardata_series[bardata_first].data; | ||||
| 	else  | ||||
| 		data = bardata_series[serie_label].data; | ||||
| 	if (data.hasOwnProperty(category)) | ||||
| 		data[category] = (data[category] + value); | ||||
| 	else | ||||
| 		data[category] = value; | ||||
| 	redraw(); | ||||
|     } | ||||
|  | ||||
|     var setbar = function(serie_label, category, value) { | ||||
|     	var data; | ||||
|  | ||||
|     	if ( serie_label == undefined ) | ||||
| 		data = bardata_series[bardata_first].data; | ||||
| 	else  | ||||
| 		data = bardata_series[serie_label].data; | ||||
| 	data[category] = value; | ||||
| 	redraw(); | ||||
|     } | ||||
|  | ||||
|     var redraw = function() { | ||||
|         plot.setData(translate_bar()); | ||||
| 	plot.setupGrid(); | ||||
|         plot.draw(); | ||||
|     } | ||||
|  | ||||
|     var reset = function() { | ||||
| 	for(var k in bardata_series) { | ||||
| 	    if (bardata_series.hasOwnProperty(k)) { | ||||
| 		bardata_series[k].data = {}; | ||||
| 	    } | ||||
| 	} | ||||
|     } | ||||
|  | ||||
|     this.actions({ | ||||
|         'set': function(e, message) { | ||||
| 		setbar(message.series,message.value[0],message.value[1]); | ||||
|         }, | ||||
|         'add': function(e, message) { | ||||
| 		addbar(message.series,message.value[0],message.value[1]); | ||||
|         }, | ||||
|         'reset': function(e, message) { | ||||
| 		reset(); | ||||
| 	} | ||||
|     }); | ||||
|     // return element to allow further work | ||||
|     return this.$element; | ||||
| } | ||||
|  | ||||
| // | ||||
| // | ||||
| // | ||||
|  | ||||
| // a simple piechart example | ||||
| block.fn.piechart = function(config) { | ||||
|     var options = $.extend({ | ||||
|     	// see: http://www.flotcharts.org/flot/examples/series-pie/ | ||||
|         filter_function : function(category,val,max) { return true; }, | ||||
|         options : { | ||||
| 		series: {	 | ||||
| 			pie: { | ||||
|                 		show: true | ||||
|         		} | ||||
|     		}, | ||||
| 		// demo crashes with this option | ||||
| 		// legend: { show: false } | ||||
|     }}, config); | ||||
|  | ||||
|     // create empty piechart with parameter options | ||||
|     var plot = $.plot(this.$element, [],options.options); | ||||
|  | ||||
|     // dict containing the labels and values | ||||
|     var piedata_dict = {}; | ||||
|  | ||||
|     var addpie = function(label, value) { | ||||
| 	if (piedata_dict.hasOwnProperty(label)) | ||||
| 		piedata_dict[label] = (piedata_dict[label] + value); | ||||
| 	else | ||||
| 		piedata_dict[label] = value; | ||||
| 	redraw(); | ||||
|     } | ||||
|  | ||||
|     var setpie = function(label, value) { | ||||
| 	piedata_dict[label] = value; | ||||
| 	redraw(); | ||||
|     } | ||||
|  | ||||
|     var redraw = function() { | ||||
|         var result = []; | ||||
| 	var max = 0; | ||||
|  | ||||
| 	for(var k in piedata_dict) { | ||||
| 	    if (piedata_dict.hasOwnProperty(k)) { | ||||
|                 max = Math.max(max, piedata_dict[k]); | ||||
| 	    } | ||||
| 	} | ||||
| 	for(var k in piedata_dict) { | ||||
| 	    if (piedata_dict.hasOwnProperty(k)) { | ||||
| 		if ( options.filter_function(k,piedata_dict[k],max) ) | ||||
|  		    result.push({label:k,data:piedata_dict[k]}); | ||||
| 	    } | ||||
| 	} | ||||
|         plot.setData(result); | ||||
|         plot.setupGrid(); | ||||
|         plot.draw(); | ||||
|     } | ||||
|  | ||||
|     var reset = function() { | ||||
| 	piedata_dict = {}; | ||||
|     } | ||||
|  | ||||
|     this.actions({ | ||||
|         'set': function(e, message) { | ||||
| 	    setpie(message.value[0],message.value[1]); | ||||
|         }, | ||||
|         'add': function(e, message) { | ||||
| 	    addpie(message.value[0],message.value[1]); | ||||
|         }, | ||||
|         'reset': function(e, message) { | ||||
| 	    reset(); | ||||
| 	} | ||||
|     }); | ||||
|     // return element to allow further work | ||||
|     return this.$element; | ||||
| } | ||||
|  | ||||
| })(jQuery, block); | ||||
							
								
								
									
										105
									
								
								template_static/lib/core.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								template_static/lib/core.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| /* | ||||
| Event stream handling. | ||||
|  | ||||
| See https://developer.mozilla.org/en-US/docs/Web/API/EventSource for a more | ||||
| comprehensive explanation. | ||||
| */ | ||||
|  | ||||
| events = {}; | ||||
|  | ||||
| (function($, exports) { | ||||
|     var e = new EventSource('/events'); | ||||
|  | ||||
|     exports.connect = function(name, elements) { | ||||
|         // wrap to allow selector, jQuery object and DOM nodes | ||||
|         var $elements = $(elements); | ||||
|  | ||||
|         // add listener that triggers events in DOM | ||||
|         this.listen(name, function(message) { | ||||
|             $elements.trigger('server-event', [message]); | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     exports.listen = function(name, callback) { | ||||
|         // add event listener to event stream | ||||
|         e.addEventListener(name, function(m) { | ||||
|             try { | ||||
|                 var message = JSON.parse(m.data); | ||||
|             } catch(err) { | ||||
|                 console.exception("Received malformed message: ",err); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             callback(message); | ||||
|         }); | ||||
|     }; | ||||
| })(jQuery, events); | ||||
|  | ||||
| /* | ||||
| ** Block fucntion allows for quick creation of new block types. | ||||
| */ | ||||
| (function($) { | ||||
|     var ConstructionState = function($element) { | ||||
|         this.$element = $element; | ||||
|  | ||||
|         // transfer all block constructors to scope | ||||
|         for(var b in block.fn) { | ||||
|             // prevent overrides | ||||
|             if(!(b in this)) { | ||||
|                 // reference block type in this object | ||||
|                 this[b] = block.fn[b]; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     ConstructionState.prototype.actions = function(actions_or_def, def) { | ||||
|         // handle function overloading | ||||
|         if(typeof actions_or_def == 'function') { | ||||
|             def = actions_or_def; | ||||
|             actions = {}; | ||||
|         } else { | ||||
|             actions = actions_or_def; | ||||
|         } | ||||
|  | ||||
|         // default actionless handler | ||||
|         if(typeof def == 'undefined') { | ||||
|             def = function(e, message) { | ||||
|                 console.error("Received actionless server event." + | ||||
|                               " Did you forget to set an action field?"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // dispatch all incoming server events | ||||
|         this.$element.on('server-event', function(e, message) { | ||||
|             if(!('action' in message)) { | ||||
|                 $(this).trigger('_default.server-event', [message]); | ||||
|             } else { | ||||
|                 $(this).trigger(message.action+'.server-event', [message]); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // bind all actions | ||||
|         this.$element.on('_default.server-event', def); | ||||
|  | ||||
|         for(var k in actions) { | ||||
|             this.$element.on(k+'.server-event', actions[k]); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     block = function(elements) { | ||||
|         // allow passing of selectors, jquery objects and DOM nodes | ||||
|         var $element = $(elements); | ||||
|  | ||||
|         // actual work | ||||
|         if($element.length != 1) { | ||||
|             console.error("Must have one element to create block for." + | ||||
|                           " Was given: '",elements,"'"); | ||||
|             return; | ||||
|         }  | ||||
|  | ||||
|         return new ConstructionState($element); | ||||
|     } | ||||
|  | ||||
|     block.fn = {}; | ||||
| })(jQuery); | ||||
							
								
								
									
										72
									
								
								template_static/lib/form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								template_static/lib/form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| (function($, block) { | ||||
| block.fn.form = function(config) { | ||||
|     var options = $.extend({ | ||||
|         target: null, | ||||
|         callback: function() {} | ||||
|     }, config); | ||||
|  | ||||
|     // see if we can grab the action from the form tag | ||||
|     if(options.target === null) { | ||||
|         var action = this.$element.find("form").attr('action'); | ||||
|         if(typeof action !== 'undefined') { | ||||
|             options.target = action; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // check for sane config | ||||
|     if(options.target === null) { | ||||
|         console.log("The 'form' block requires a target option to know where to send the request."); | ||||
|         return this.$element; | ||||
|     } | ||||
|  | ||||
|     // set up submit handler | ||||
|     var $block = this.$element; | ||||
|     $block.find("form").submit(function(event) { | ||||
|         var payload = {}; | ||||
|  | ||||
|         // handle simple fields | ||||
|         $block.find("textarea[name], select[name]").each(function() { | ||||
|             payload[this.name] = this.value; | ||||
|         }); | ||||
|  | ||||
|         // handle the more complex fields | ||||
|         $block.find("input[name]").each(function() { | ||||
|             switch($(this).attr('type')) { | ||||
|                 // radio buttons usually have a single selected option per name | ||||
|                 case 'radio': | ||||
|                     if($(this).prop('checked')) { | ||||
|                         payload[this.name] = this.value; | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 // checkboxes are akin to a bitfield | ||||
|                 case 'checkbox': | ||||
|                     // build a map of checked values for this name | ||||
|                     if(typeof payload[this.name] === 'undefined') { | ||||
|                         payload[this.name] = []; | ||||
|                     } | ||||
|                     if($(this).prop('checked')) { | ||||
|                         payload[this.name].push(this.value); | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
|                 // default to storing the value | ||||
|                 default: | ||||
|                     payload[this.name] = this.value; | ||||
|                     break; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         event.preventDefault(); | ||||
|  | ||||
|         // fire and forget the datablob | ||||
|         $.ajax(options.target,{ | ||||
|             method: 'POST', | ||||
|             data: JSON.stringify(payload) | ||||
|         }).then(options.callback); | ||||
|     }); | ||||
|  | ||||
|     return this.$element; | ||||
| } | ||||
|  | ||||
| })(jQuery, block); | ||||
							
								
								
									
										232
									
								
								template_static/lib/jqcloud-1.0.4.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								template_static/lib/jqcloud-1.0.4.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,232 @@ | ||||
| /*! | ||||
|  * jQCloud Plugin for jQuery | ||||
|  * | ||||
|  * Version 1.0.4 | ||||
|  * | ||||
|  * Copyright 2011, Luca Ongaro | ||||
|  * Licensed under the MIT license. | ||||
|  * | ||||
|  * Date: 2013-05-09 18:54:22 +0200 | ||||
| */ | ||||
|  | ||||
| (function( $ ) { | ||||
|   "use strict"; | ||||
|   $.fn.jQCloud = function(word_array, options) { | ||||
|     // Reference to the container element | ||||
|     var $this = this; | ||||
|     // Namespace word ids to avoid collisions between multiple clouds | ||||
|     var cloud_namespace = $this.attr('id') || Math.floor((Math.random()*1000000)).toString(36); | ||||
|  | ||||
|     // Default options value | ||||
|     var default_options = { | ||||
|       width: $this.width(), | ||||
|       height: $this.height(), | ||||
|       center: { | ||||
|         x: ((options && options.width) ? options.width : $this.width()) / 2.0, | ||||
|         y: ((options && options.height) ? options.height : $this.height()) / 2.0 | ||||
|       }, | ||||
|       delayedMode: word_array.length > 50, | ||||
|       shape: false, // It defaults to elliptic shape | ||||
|       encodeURI: true, | ||||
|       removeOverflowing: true | ||||
|     }; | ||||
|  | ||||
|     options = $.extend(default_options, options || {}); | ||||
|  | ||||
|     // Add the "jqcloud" class to the container for easy CSS styling, set container width/height | ||||
|     $this.addClass("jqcloud").width(options.width).height(options.height); | ||||
|  | ||||
|     // Container's CSS position cannot be 'static' | ||||
|     if ($this.css("position") === "static") { | ||||
|       $this.css("position", "relative"); | ||||
|     } | ||||
|  | ||||
|     var drawWordCloud = function() { | ||||
|       // Helper function to test if an element overlaps others | ||||
|       var hitTest = function(elem, other_elems) { | ||||
|         // Pairwise overlap detection | ||||
|         var overlapping = function(a, b) { | ||||
|           if (Math.abs(2.0*a.offsetLeft + a.offsetWidth - 2.0*b.offsetLeft - b.offsetWidth) < a.offsetWidth + b.offsetWidth) { | ||||
|             if (Math.abs(2.0*a.offsetTop + a.offsetHeight - 2.0*b.offsetTop - b.offsetHeight) < a.offsetHeight + b.offsetHeight) { | ||||
|               return true; | ||||
|             } | ||||
|           } | ||||
|           return false; | ||||
|         }; | ||||
|         var i = 0; | ||||
|         // Check elements for overlap one by one, stop and return false as soon as an overlap is found | ||||
|         for(i = 0; i < other_elems.length; i++) { | ||||
|           if (overlapping(elem, other_elems[i])) { | ||||
|             return true; | ||||
|           } | ||||
|         } | ||||
|         return false; | ||||
|       }; | ||||
|  | ||||
|       // Make sure every weight is a number before sorting | ||||
|       for (var i = 0; i < word_array.length; i++) { | ||||
|         word_array[i].weight = parseFloat(word_array[i].weight, 10); | ||||
|       } | ||||
|  | ||||
|       // Sort word_array from the word with the highest weight to the one with the lowest | ||||
|       word_array.sort(function(a, b) { if (a.weight < b.weight) {return 1;} else if (a.weight > b.weight) {return -1;} else {return 0;} }); | ||||
|  | ||||
|       var step = (options.shape === "rectangular") ? 18.0 : 2.0, | ||||
|           already_placed_words = [], | ||||
|           aspect_ratio = options.width / options.height; | ||||
|  | ||||
|       // Function to draw a word, by moving it in spiral until it finds a suitable empty place. This will be iterated on each word. | ||||
|       var drawOneWord = function(index, word) { | ||||
|         // Define the ID attribute of the span that will wrap the word, and the associated jQuery selector string | ||||
|         var word_id = cloud_namespace + "_word_" + index, | ||||
|             word_selector = "#" + word_id, | ||||
|             angle = 6.28 * Math.random(), | ||||
|             radius = 0.0, | ||||
|  | ||||
|             // Only used if option.shape == 'rectangular' | ||||
|             steps_in_direction = 0.0, | ||||
|             quarter_turns = 0.0, | ||||
|  | ||||
|             weight = 5, | ||||
|             custom_class = "", | ||||
|             inner_html = "", | ||||
|             word_span; | ||||
|  | ||||
|         // Extend word html options with defaults | ||||
|         word.html = $.extend(word.html, {id: word_id}); | ||||
|  | ||||
|         // If custom class was specified, put them into a variable and remove it from html attrs, to avoid overwriting classes set by jQCloud | ||||
|         if (word.html && word.html["class"]) { | ||||
|           custom_class = word.html["class"]; | ||||
|           delete word.html["class"]; | ||||
|         } | ||||
|  | ||||
|         // Check if min(weight) > max(weight) otherwise use default | ||||
|         if (word_array[0].weight > word_array[word_array.length - 1].weight) { | ||||
|           // Linearly map the original weight to a discrete scale from 1 to 10 | ||||
|           weight = Math.round((word.weight - word_array[word_array.length - 1].weight) / | ||||
|                               (word_array[0].weight - word_array[word_array.length - 1].weight) * 9.0) + 1; | ||||
|         } | ||||
|         word_span = $('<span>').attr(word.html).addClass('w' + weight + " " + custom_class); | ||||
|  | ||||
|         // Append link if word.url attribute was set | ||||
|         if (word.link) { | ||||
|           // If link is a string, then use it as the link href | ||||
|           if (typeof word.link === "string") { | ||||
|             word.link = {href: word.link}; | ||||
|           } | ||||
|  | ||||
|           // Extend link html options with defaults | ||||
|           if ( options.encodeURI ) { | ||||
|             word.link = $.extend(word.link, { href: encodeURI(word.link.href).replace(/'/g, "%27") }); | ||||
|           } | ||||
|  | ||||
|           inner_html = $('<a>').attr(word.link).text(word.text); | ||||
|         } else { | ||||
|           inner_html = word.text; | ||||
|         } | ||||
|         word_span.append(inner_html); | ||||
|  | ||||
|         // Bind handlers to words | ||||
|         if (!!word.handlers) { | ||||
|           for (var prop in word.handlers) { | ||||
|             if (word.handlers.hasOwnProperty(prop) && typeof word.handlers[prop] === 'function') { | ||||
|               $(word_span).bind(prop, word.handlers[prop]); | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         $this.append(word_span); | ||||
|  | ||||
|         var width = word_span.width(), | ||||
|             height = word_span.height(), | ||||
|             left = options.center.x - width / 2.0, | ||||
|             top = options.center.y - height / 2.0; | ||||
|  | ||||
|         // Save a reference to the style property, for better performance | ||||
|         var word_style = word_span[0].style; | ||||
|         word_style.position = "absolute"; | ||||
|         word_style.left = left + "px"; | ||||
|         word_style.top = top + "px"; | ||||
|  | ||||
|         while(hitTest(word_span[0], already_placed_words)) { | ||||
|           // option shape is 'rectangular' so move the word in a rectangular spiral | ||||
|           if (options.shape === "rectangular") { | ||||
|             steps_in_direction++; | ||||
|             if (steps_in_direction * step > (1 + Math.floor(quarter_turns / 2.0)) * step * ((quarter_turns % 4 % 2) === 0 ? 1 : aspect_ratio)) { | ||||
|               steps_in_direction = 0.0; | ||||
|               quarter_turns++; | ||||
|             } | ||||
|             switch(quarter_turns % 4) { | ||||
|               case 1: | ||||
|                 left += step * aspect_ratio + Math.random() * 2.0; | ||||
|                 break; | ||||
|               case 2: | ||||
|                 top -= step + Math.random() * 2.0; | ||||
|                 break; | ||||
|               case 3: | ||||
|                 left -= step * aspect_ratio + Math.random() * 2.0; | ||||
|                 break; | ||||
|               case 0: | ||||
|                 top += step + Math.random() * 2.0; | ||||
|                 break; | ||||
|             } | ||||
|           } else { // Default settings: elliptic spiral shape | ||||
|             radius += step; | ||||
|             angle += (index % 2 === 0 ? 1 : -1)*step; | ||||
|  | ||||
|             left = options.center.x - (width / 2.0) + (radius*Math.cos(angle)) * aspect_ratio; | ||||
|             top = options.center.y + radius*Math.sin(angle) - (height / 2.0); | ||||
|           } | ||||
|           word_style.left = left + "px"; | ||||
|           word_style.top = top + "px"; | ||||
|         } | ||||
|  | ||||
|         // Don't render word if part of it would be outside the container | ||||
|         if (options.removeOverflowing && (left < 0 || top < 0 || (left + width) > options.width || (top + height) > options.height)) { | ||||
|           word_span.remove() | ||||
|           return; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         already_placed_words.push(word_span[0]); | ||||
|  | ||||
|         // Invoke callback if existing | ||||
|         if ($.isFunction(word.afterWordRender)) { | ||||
|           word.afterWordRender.call(word_span); | ||||
|         } | ||||
|       }; | ||||
|  | ||||
|       var drawOneWordDelayed = function(index) { | ||||
|         index = index || 0; | ||||
|         if (!$this.is(':visible')) { // if not visible then do not attempt to draw | ||||
|           setTimeout(function(){drawOneWordDelayed(index);},10); | ||||
|           return; | ||||
|         } | ||||
|         if (index < word_array.length) { | ||||
|           drawOneWord(index, word_array[index]); | ||||
|           setTimeout(function(){drawOneWordDelayed(index + 1);}, 10); | ||||
|         } else { | ||||
|           if ($.isFunction(options.afterCloudRender)) { | ||||
|             options.afterCloudRender.call($this); | ||||
|           } | ||||
|         } | ||||
|       }; | ||||
|  | ||||
|       // Iterate drawOneWord on every word. The way the iteration is done depends on the drawing mode (delayedMode is true or false) | ||||
|       if (options.delayedMode){ | ||||
|         drawOneWordDelayed(); | ||||
|       } | ||||
|       else { | ||||
|         $.each(word_array, drawOneWord); | ||||
|         if ($.isFunction(options.afterCloudRender)) { | ||||
|           options.afterCloudRender.call($this); | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     // Delay execution so that the browser can render the page before the computatively intensive word cloud drawing | ||||
|     setTimeout(function(){drawWordCloud();}, 10); | ||||
|     return $this; | ||||
|   }; | ||||
| })(jQuery); | ||||
							
								
								
									
										4
									
								
								template_static/lib/jquery-2.1.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								template_static/lib/jquery-2.1.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										44
									
								
								template_static/lib/jquery.flot.categories.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								template_static/lib/jquery.flot.categories.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| /* Flot plugin for plotting textual data or categories. | ||||
|  | ||||
| Copyright (c) 2007-2013 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
|  | ||||
| Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin | ||||
| allows you to plot such a dataset directly. | ||||
|  | ||||
| To enable it, you must specify mode: "categories" on the axis with the textual | ||||
| labels, e.g. | ||||
|  | ||||
| 	$.plot("#placeholder", data, { xaxis: { mode: "categories" } }); | ||||
|  | ||||
| By default, the labels are ordered as they are met in the data series. If you | ||||
| need a different ordering, you can specify "categories" on the axis options | ||||
| and list the categories there: | ||||
|  | ||||
| 	xaxis: { | ||||
| 		mode: "categories", | ||||
| 		categories: ["February", "March", "April"] | ||||
| 	} | ||||
|  | ||||
| If you need to customize the distances between the categories, you can specify | ||||
| "categories" as an object mapping labels to values | ||||
|  | ||||
| 	xaxis: { | ||||
| 		mode: "categories", | ||||
| 		categories: { "February": 1, "March": 3, "April": 4 } | ||||
| 	} | ||||
|  | ||||
| If you don't specify all categories, the remaining categories will be numbered | ||||
| from the max value plus 1 (with a spacing of 1 between each). | ||||
|  | ||||
| Internally, the plugin works by transforming the input data through an auto- | ||||
| generated mapping where the first category becomes 0, the second 1, etc. | ||||
| Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this | ||||
| is visible in hover and click events that return numbers rather than the | ||||
| category labels). The plugin also overrides the tick generator to spit out the | ||||
| categories as ticks instead of the values. | ||||
|  | ||||
| If you need to map a value back to its label, the mapping is always accessible | ||||
| as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories. | ||||
|  | ||||
| */(function(e){function n(e,t,n,r){var i=t.xaxis.options.mode=="categories",s=t.yaxis.options.mode=="categories";if(!i&&!s)return;var o=r.format;if(!o){var u=t;o=[],o.push({x:!0,number:!0,required:!0}),o.push({y:!0,number:!0,required:!0});if(u.bars.show||u.lines.show&&u.lines.fill){var a=!!(u.bars.show&&u.bars.zero||u.lines.show&&u.lines.zero);o.push({y:!0,number:!0,required:!1,defaultValue:0,autoscale:a}),u.bars.horizontal&&(delete o[o.length-1].y,o[o.length-1].x=!0)}r.format=o}for(var f=0;f<o.length;++f)o[f].x&&i&&(o[f].number=!1),o[f].y&&s&&(o[f].number=!1)}function r(e){var t=-1;for(var n in e)e[n]>t&&(t=e[n]);return t+1}function i(e){var t=[];for(var n in e.categories){var r=e.categories[n];r>=e.min&&r<=e.max&&t.push([r,n])}return t.sort(function(e,t){return e[0]-t[0]}),t}function s(t,n,r){if(t[n].options.mode!="categories")return;if(!t[n].categories){var s={},u=t[n].options.categories||{};if(e.isArray(u))for(var a=0;a<u.length;++a)s[u[a]]=a;else for(var f in u)s[f]=u[f];t[n].categories=s}t[n].options.ticks||(t[n].options.ticks=i),o(r,n,t[n].categories)}function o(e,t,n){var i=e.points,s=e.pointsize,o=e.format,u=t.charAt(0),a=r(n);for(var f=0;f<i.length;f+=s){if(i[f]==null)continue;for(var l=0;l<s;++l){var c=i[f+l];if(c==null||!o[l][u])continue;c in n||(n[c]=a,++a),i[f+l]=n[c]}}}function u(e,t,n){s(t,"xaxis",n),s(t,"yaxis",n)}function a(e){e.hooks.processRawData.push(n),e.hooks.processDatapoints.push(u)}var t={xaxis:{categories:null},yaxis:{categories:null}};e.plot.plugins.push({init:a,options:t,name:"categories",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										8
									
								
								template_static/lib/jquery.flot.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								template_static/lib/jquery.flot.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										820
									
								
								template_static/lib/jquery.flot.pie.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										820
									
								
								template_static/lib/jquery.flot.pie.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,820 @@ | ||||
| /* Flot plugin for rendering pie charts. | ||||
|  | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
|  | ||||
| The plugin assumes that each series has a single data value, and that each | ||||
| value is a positive integer or zero.  Negative numbers don't make sense for a | ||||
| pie chart, and have unpredictable results.  The values do NOT need to be | ||||
| passed in as percentages; the plugin will calculate the total and per-slice | ||||
| percentages internally. | ||||
|  | ||||
| * Created by Brian Medendorp | ||||
|  | ||||
| * Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars | ||||
|  | ||||
| The plugin supports these options: | ||||
|  | ||||
| 	series: { | ||||
| 		pie: { | ||||
| 			show: true/false | ||||
| 			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' | ||||
| 			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect | ||||
| 			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result | ||||
| 			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) | ||||
| 			offset: { | ||||
| 				top: integer value to move the pie up or down | ||||
| 				left: integer value to move the pie left or right, or 'auto' | ||||
| 			}, | ||||
| 			stroke: { | ||||
| 				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') | ||||
| 				width: integer pixel width of the stroke | ||||
| 			}, | ||||
| 			label: { | ||||
| 				show: true/false, or 'auto' | ||||
| 				formatter:  a user-defined function that modifies the text/style of the label text | ||||
| 				radius: 0-1 for percentage of fullsize, or a specified pixel length | ||||
| 				background: { | ||||
| 					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') | ||||
| 					opacity: 0-1 | ||||
| 				}, | ||||
| 				threshold: 0-1 for the percentage value at which to hide labels (if they're too small) | ||||
| 			}, | ||||
| 			combine: { | ||||
| 				threshold: 0-1 for the percentage value at which to combine slices (if they're too small) | ||||
| 				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined | ||||
| 				label: any text value of what the combined slice should be labeled | ||||
| 			} | ||||
| 			highlight: { | ||||
| 				opacity: 0-1 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| More detail and specific examples can be found in the included HTML file. | ||||
|  | ||||
| */ | ||||
|  | ||||
| (function($) { | ||||
|  | ||||
| 	// Maximum redraw attempts when fitting labels within the plot | ||||
|  | ||||
| 	var REDRAW_ATTEMPTS = 10; | ||||
|  | ||||
| 	// Factor by which to shrink the pie when fitting labels within the plot | ||||
|  | ||||
| 	var REDRAW_SHRINK = 0.95; | ||||
|  | ||||
| 	function init(plot) { | ||||
|  | ||||
| 		var canvas = null, | ||||
| 			target = null, | ||||
| 			options = null, | ||||
| 			maxRadius = null, | ||||
| 			centerLeft = null, | ||||
| 			centerTop = null, | ||||
| 			processed = false, | ||||
| 			ctx = null; | ||||
|  | ||||
| 		// interactive variables | ||||
|  | ||||
| 		var highlights = []; | ||||
|  | ||||
| 		// add hook to determine if pie plugin in enabled, and then perform necessary operations | ||||
|  | ||||
| 		plot.hooks.processOptions.push(function(plot, options) { | ||||
| 			if (options.series.pie.show) { | ||||
|  | ||||
| 				options.grid.show = false; | ||||
|  | ||||
| 				// set labels.show | ||||
|  | ||||
| 				if (options.series.pie.label.show == "auto") { | ||||
| 					if (options.legend.show) { | ||||
| 						options.series.pie.label.show = false; | ||||
| 					} else { | ||||
| 						options.series.pie.label.show = true; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// set radius | ||||
|  | ||||
| 				if (options.series.pie.radius == "auto") { | ||||
| 					if (options.series.pie.label.show) { | ||||
| 						options.series.pie.radius = 3/4; | ||||
| 					} else { | ||||
| 						options.series.pie.radius = 1; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// ensure sane tilt | ||||
|  | ||||
| 				if (options.series.pie.tilt > 1) { | ||||
| 					options.series.pie.tilt = 1; | ||||
| 				} else if (options.series.pie.tilt < 0) { | ||||
| 					options.series.pie.tilt = 0; | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		plot.hooks.bindEvents.push(function(plot, eventHolder) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				if (options.grid.hoverable) { | ||||
| 					eventHolder.unbind("mousemove").mousemove(onMouseMove); | ||||
| 				} | ||||
| 				if (options.grid.clickable) { | ||||
| 					eventHolder.unbind("click").click(onClick); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				processDatapoints(plot, series, data, datapoints); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		plot.hooks.drawOverlay.push(function(plot, octx) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				drawOverlay(plot, octx); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		plot.hooks.draw.push(function(plot, newCtx) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				draw(plot, newCtx); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		function processDatapoints(plot, series, datapoints) { | ||||
| 			if (!processed)	{ | ||||
| 				processed = true; | ||||
| 				canvas = plot.getCanvas(); | ||||
| 				target = $(canvas).parent(); | ||||
| 				options = plot.getOptions(); | ||||
| 				plot.setData(combine(plot.getData())); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		function combine(data) { | ||||
|  | ||||
| 			var total = 0, | ||||
| 				combined = 0, | ||||
| 				numCombined = 0, | ||||
| 				color = options.series.pie.combine.color, | ||||
| 				newdata = []; | ||||
|  | ||||
| 			// Fix up the raw data from Flot, ensuring the data is numeric | ||||
|  | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
|  | ||||
| 				var value = data[i].data; | ||||
|  | ||||
| 				// If the data is an array, we'll assume that it's a standard | ||||
| 				// Flot x-y pair, and are concerned only with the second value. | ||||
|  | ||||
| 				// Note how we use the original array, rather than creating a | ||||
| 				// new one; this is more efficient and preserves any extra data | ||||
| 				// that the user may have stored in higher indexes. | ||||
|  | ||||
| 				if ($.isArray(value) && value.length == 1) { | ||||
|     				value = value[0]; | ||||
| 				} | ||||
|  | ||||
| 				if ($.isArray(value)) { | ||||
| 					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7 | ||||
| 					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { | ||||
| 						value[1] = +value[1]; | ||||
| 					} else { | ||||
| 						value[1] = 0; | ||||
| 					} | ||||
| 				} else if (!isNaN(parseFloat(value)) && isFinite(value)) { | ||||
| 					value = [1, +value]; | ||||
| 				} else { | ||||
| 					value = [1, 0]; | ||||
| 				} | ||||
|  | ||||
| 				data[i].data = [value]; | ||||
| 			} | ||||
|  | ||||
| 			// Sum up all the slices, so we can calculate percentages for each | ||||
|  | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 				total += data[i].data[0][1]; | ||||
| 			} | ||||
|  | ||||
| 			// Count the number of slices with percentages below the combine | ||||
| 			// threshold; if it turns out to be just one, we won't combine. | ||||
|  | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 				var value = data[i].data[0][1]; | ||||
| 				if (value / total <= options.series.pie.combine.threshold) { | ||||
| 					combined += value; | ||||
| 					numCombined++; | ||||
| 					if (!color) { | ||||
| 						color = data[i].color; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 				var value = data[i].data[0][1]; | ||||
| 				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { | ||||
| 					newdata.push( | ||||
| 						$.extend(data[i], {     /* extend to allow keeping all other original data values | ||||
| 						                           and using them e.g. in labelFormatter. */ | ||||
| 							data: [[1, value]], | ||||
| 							color: data[i].color, | ||||
| 							label: data[i].label, | ||||
| 							angle: value * Math.PI * 2 / total, | ||||
| 							percent: value / (total / 100) | ||||
| 						}) | ||||
| 					); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (numCombined > 1) { | ||||
| 				newdata.push({ | ||||
| 					data: [[1, combined]], | ||||
| 					color: color, | ||||
| 					label: options.series.pie.combine.label, | ||||
| 					angle: combined * Math.PI * 2 / total, | ||||
| 					percent: combined / (total / 100) | ||||
| 				}); | ||||
| 			} | ||||
|  | ||||
| 			return newdata; | ||||
| 		} | ||||
|  | ||||
| 		function draw(plot, newCtx) { | ||||
|  | ||||
| 			if (!target) { | ||||
| 				return; // if no series were passed | ||||
| 			} | ||||
|  | ||||
| 			var canvasWidth = plot.getPlaceholder().width(), | ||||
| 				canvasHeight = plot.getPlaceholder().height(), | ||||
| 				legendWidth = target.children().filter(".legend").children().width() || 0; | ||||
|  | ||||
| 			ctx = newCtx; | ||||
|  | ||||
| 			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! | ||||
|  | ||||
| 			// When combining smaller slices into an 'other' slice, we need to | ||||
| 			// add a new series.  Since Flot gives plugins no way to modify the | ||||
| 			// list of series, the pie plugin uses a hack where the first call | ||||
| 			// to processDatapoints results in a call to setData with the new | ||||
| 			// list of series, then subsequent processDatapoints do nothing. | ||||
|  | ||||
| 			// The plugin-global 'processed' flag is used to control this hack; | ||||
| 			// it starts out false, and is set to true after the first call to | ||||
| 			// processDatapoints. | ||||
|  | ||||
| 			// Unfortunately this turns future setData calls into no-ops; they | ||||
| 			// call processDatapoints, the flag is true, and nothing happens. | ||||
|  | ||||
| 			// To fix this we'll set the flag back to false here in draw, when | ||||
| 			// all series have been processed, so the next sequence of calls to | ||||
| 			// processDatapoints once again starts out with a slice-combine. | ||||
| 			// This is really a hack; in 0.9 we need to give plugins a proper | ||||
| 			// way to modify series before any processing begins. | ||||
|  | ||||
| 			processed = false; | ||||
|  | ||||
| 			// calculate maximum radius and center point | ||||
|  | ||||
| 			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; | ||||
| 			centerTop = canvasHeight / 2 + options.series.pie.offset.top; | ||||
| 			centerLeft = canvasWidth / 2; | ||||
|  | ||||
| 			if (options.series.pie.offset.left == "auto") { | ||||
| 				if (options.legend.position.match("w")) { | ||||
| 					centerLeft += legendWidth / 2; | ||||
| 				} else { | ||||
| 					centerLeft -= legendWidth / 2; | ||||
| 				} | ||||
| 				if (centerLeft < maxRadius) { | ||||
| 					centerLeft = maxRadius; | ||||
| 				} else if (centerLeft > canvasWidth - maxRadius) { | ||||
| 					centerLeft = canvasWidth - maxRadius; | ||||
| 				} | ||||
| 			} else { | ||||
| 				centerLeft += options.series.pie.offset.left; | ||||
| 			} | ||||
|  | ||||
| 			var slices = plot.getData(), | ||||
| 				attempts = 0; | ||||
|  | ||||
| 			// Keep shrinking the pie's radius until drawPie returns true, | ||||
| 			// indicating that all the labels fit, or we try too many times. | ||||
|  | ||||
| 			do { | ||||
| 				if (attempts > 0) { | ||||
| 					maxRadius *= REDRAW_SHRINK; | ||||
| 				} | ||||
| 				attempts += 1; | ||||
| 				clear(); | ||||
| 				if (options.series.pie.tilt <= 0.8) { | ||||
| 					drawShadow(); | ||||
| 				} | ||||
| 			} while (!drawPie() && attempts < REDRAW_ATTEMPTS) | ||||
|  | ||||
| 			if (attempts >= REDRAW_ATTEMPTS) { | ||||
| 				clear(); | ||||
| 				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>"); | ||||
| 			} | ||||
|  | ||||
| 			if (plot.setSeries && plot.insertLegend) { | ||||
| 				plot.setSeries(slices); | ||||
| 				plot.insertLegend(); | ||||
| 			} | ||||
|  | ||||
| 			// we're actually done at this point, just defining internal functions at this point | ||||
|  | ||||
| 			function clear() { | ||||
| 				ctx.clearRect(0, 0, canvasWidth, canvasHeight); | ||||
| 				target.children().filter(".pieLabel, .pieLabelBackground").remove(); | ||||
| 			} | ||||
|  | ||||
| 			function drawShadow() { | ||||
|  | ||||
| 				var shadowLeft = options.series.pie.shadow.left; | ||||
| 				var shadowTop = options.series.pie.shadow.top; | ||||
| 				var edge = 10; | ||||
| 				var alpha = options.series.pie.shadow.alpha; | ||||
| 				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; | ||||
|  | ||||
| 				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { | ||||
| 					return;	// shadow would be outside canvas, so don't draw it | ||||
| 				} | ||||
|  | ||||
| 				ctx.save(); | ||||
| 				ctx.translate(shadowLeft,shadowTop); | ||||
| 				ctx.globalAlpha = alpha; | ||||
| 				ctx.fillStyle = "#000"; | ||||
|  | ||||
| 				// center and rotate to starting position | ||||
|  | ||||
| 				ctx.translate(centerLeft,centerTop); | ||||
| 				ctx.scale(1, options.series.pie.tilt); | ||||
|  | ||||
| 				//radius -= edge; | ||||
|  | ||||
| 				for (var i = 1; i <= edge; i++) { | ||||
| 					ctx.beginPath(); | ||||
| 					ctx.arc(0, 0, radius, 0, Math.PI * 2, false); | ||||
| 					ctx.fill(); | ||||
| 					radius -= i; | ||||
| 				} | ||||
|  | ||||
| 				ctx.restore(); | ||||
| 			} | ||||
|  | ||||
| 			function drawPie() { | ||||
|  | ||||
| 				var startAngle = Math.PI * options.series.pie.startAngle; | ||||
| 				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; | ||||
|  | ||||
| 				// center and rotate to starting position | ||||
|  | ||||
| 				ctx.save(); | ||||
| 				ctx.translate(centerLeft,centerTop); | ||||
| 				ctx.scale(1, options.series.pie.tilt); | ||||
| 				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera | ||||
|  | ||||
| 				// draw slices | ||||
|  | ||||
| 				ctx.save(); | ||||
| 				var currentAngle = startAngle; | ||||
| 				for (var i = 0; i < slices.length; ++i) { | ||||
| 					slices[i].startAngle = currentAngle; | ||||
| 					drawSlice(slices[i].angle, slices[i].color, true); | ||||
| 				} | ||||
| 				ctx.restore(); | ||||
|  | ||||
| 				// draw slice outlines | ||||
|  | ||||
| 				if (options.series.pie.stroke.width > 0) { | ||||
| 					ctx.save(); | ||||
| 					ctx.lineWidth = options.series.pie.stroke.width; | ||||
| 					currentAngle = startAngle; | ||||
| 					for (var i = 0; i < slices.length; ++i) { | ||||
| 						drawSlice(slices[i].angle, options.series.pie.stroke.color, false); | ||||
| 					} | ||||
| 					ctx.restore(); | ||||
| 				} | ||||
|  | ||||
| 				// draw donut hole | ||||
|  | ||||
| 				drawDonutHole(ctx); | ||||
|  | ||||
| 				ctx.restore(); | ||||
|  | ||||
| 				// Draw the labels, returning true if they fit within the plot | ||||
|  | ||||
| 				if (options.series.pie.label.show) { | ||||
| 					return drawLabels(); | ||||
| 				} else return true; | ||||
|  | ||||
| 				function drawSlice(angle, color, fill) { | ||||
|  | ||||
| 					if (angle <= 0 || isNaN(angle)) { | ||||
| 						return; | ||||
| 					} | ||||
|  | ||||
| 					if (fill) { | ||||
| 						ctx.fillStyle = color; | ||||
| 					} else { | ||||
| 						ctx.strokeStyle = color; | ||||
| 						ctx.lineJoin = "round"; | ||||
| 					} | ||||
|  | ||||
| 					ctx.beginPath(); | ||||
| 					if (Math.abs(angle - Math.PI * 2) > 0.000000001) { | ||||
| 						ctx.moveTo(0, 0); // Center of the pie | ||||
| 					} | ||||
|  | ||||
| 					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera | ||||
| 					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); | ||||
| 					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); | ||||
| 					ctx.closePath(); | ||||
| 					//ctx.rotate(angle); // This doesn't work properly in Opera | ||||
| 					currentAngle += angle; | ||||
|  | ||||
| 					if (fill) { | ||||
| 						ctx.fill(); | ||||
| 					} else { | ||||
| 						ctx.stroke(); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				function drawLabels() { | ||||
|  | ||||
| 					var currentAngle = startAngle; | ||||
| 					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; | ||||
|  | ||||
| 					for (var i = 0; i < slices.length; ++i) { | ||||
| 						if (slices[i].percent >= options.series.pie.label.threshold * 100) { | ||||
| 							if (!drawLabel(slices[i], currentAngle, i)) { | ||||
| 								return false; | ||||
| 							} | ||||
| 						} | ||||
| 						currentAngle += slices[i].angle; | ||||
| 					} | ||||
|  | ||||
| 					return true; | ||||
|  | ||||
| 					function drawLabel(slice, startAngle, index) { | ||||
|  | ||||
| 						if (slice.data[0][1] == 0) { | ||||
| 							return true; | ||||
| 						} | ||||
|  | ||||
| 						// format label text | ||||
|  | ||||
| 						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; | ||||
|  | ||||
| 						if (lf) { | ||||
| 							text = lf(slice.label, slice); | ||||
| 						} else { | ||||
| 							text = slice.label; | ||||
| 						} | ||||
|  | ||||
| 						if (plf) { | ||||
| 							text = plf(text, slice); | ||||
| 						} | ||||
|  | ||||
| 						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; | ||||
| 						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); | ||||
| 						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; | ||||
|  | ||||
| 						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>"; | ||||
| 						target.append(html); | ||||
|  | ||||
| 						var label = target.children("#pieLabel" + index); | ||||
| 						var labelTop = (y - label.height() / 2); | ||||
| 						var labelLeft = (x - label.width() / 2); | ||||
|  | ||||
| 						label.css("top", labelTop); | ||||
| 						label.css("left", labelLeft); | ||||
|  | ||||
| 						// check to make sure that the label is not outside the canvas | ||||
|  | ||||
| 						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { | ||||
| 							return false; | ||||
| 						} | ||||
|  | ||||
| 						if (options.series.pie.label.background.opacity != 0) { | ||||
|  | ||||
| 							// put in the transparent background separately to avoid blended labels and label boxes | ||||
|  | ||||
| 							var c = options.series.pie.label.background.color; | ||||
|  | ||||
| 							if (c == null) { | ||||
| 								c = slice.color; | ||||
| 							} | ||||
|  | ||||
| 							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; | ||||
| 							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>") | ||||
| 								.css("opacity", options.series.pie.label.background.opacity) | ||||
| 								.insertBefore(label); | ||||
| 						} | ||||
|  | ||||
| 						return true; | ||||
| 					} // end individual label function | ||||
| 				} // end drawLabels function | ||||
| 			} // end drawPie function | ||||
| 		} // end draw function | ||||
|  | ||||
| 		// Placed here because it needs to be accessed from multiple locations | ||||
|  | ||||
| 		function drawDonutHole(layer) { | ||||
| 			if (options.series.pie.innerRadius > 0) { | ||||
|  | ||||
| 				// subtract the center | ||||
|  | ||||
| 				layer.save(); | ||||
| 				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; | ||||
| 				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color | ||||
| 				layer.beginPath(); | ||||
| 				layer.fillStyle = options.series.pie.stroke.color; | ||||
| 				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); | ||||
| 				layer.fill(); | ||||
| 				layer.closePath(); | ||||
| 				layer.restore(); | ||||
|  | ||||
| 				// add inner stroke | ||||
|  | ||||
| 				layer.save(); | ||||
| 				layer.beginPath(); | ||||
| 				layer.strokeStyle = options.series.pie.stroke.color; | ||||
| 				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); | ||||
| 				layer.stroke(); | ||||
| 				layer.closePath(); | ||||
| 				layer.restore(); | ||||
|  | ||||
| 				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted. | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		//-- Additional Interactive related functions -- | ||||
|  | ||||
| 		function isPointInPoly(poly, pt) { | ||||
| 			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) | ||||
| 				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) | ||||
| 				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) | ||||
| 				&& (c = !c); | ||||
| 			return c; | ||||
| 		} | ||||
|  | ||||
| 		function findNearbySlice(mouseX, mouseY) { | ||||
|  | ||||
| 			var slices = plot.getData(), | ||||
| 				options = plot.getOptions(), | ||||
| 				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, | ||||
| 				x, y; | ||||
|  | ||||
| 			for (var i = 0; i < slices.length; ++i) { | ||||
|  | ||||
| 				var s = slices[i]; | ||||
|  | ||||
| 				if (s.pie.show) { | ||||
|  | ||||
| 					ctx.save(); | ||||
| 					ctx.beginPath(); | ||||
| 					ctx.moveTo(0, 0); // Center of the pie | ||||
| 					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here. | ||||
| 					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); | ||||
| 					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); | ||||
| 					ctx.closePath(); | ||||
| 					x = mouseX - centerLeft; | ||||
| 					y = mouseY - centerTop; | ||||
|  | ||||
| 					if (ctx.isPointInPath) { | ||||
| 						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { | ||||
| 							ctx.restore(); | ||||
| 							return { | ||||
| 								datapoint: [s.percent, s.data], | ||||
| 								dataIndex: 0, | ||||
| 								series: s, | ||||
| 								seriesIndex: i | ||||
| 							}; | ||||
| 						} | ||||
| 					} else { | ||||
|  | ||||
| 						// excanvas for IE doesn;t support isPointInPath, this is a workaround. | ||||
|  | ||||
| 						var p1X = radius * Math.cos(s.startAngle), | ||||
| 							p1Y = radius * Math.sin(s.startAngle), | ||||
| 							p2X = radius * Math.cos(s.startAngle + s.angle / 4), | ||||
| 							p2Y = radius * Math.sin(s.startAngle + s.angle / 4), | ||||
| 							p3X = radius * Math.cos(s.startAngle + s.angle / 2), | ||||
| 							p3Y = radius * Math.sin(s.startAngle + s.angle / 2), | ||||
| 							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), | ||||
| 							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), | ||||
| 							p5X = radius * Math.cos(s.startAngle + s.angle), | ||||
| 							p5Y = radius * Math.sin(s.startAngle + s.angle), | ||||
| 							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], | ||||
| 							arrPoint = [x, y]; | ||||
|  | ||||
| 						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? | ||||
|  | ||||
| 						if (isPointInPoly(arrPoly, arrPoint)) { | ||||
| 							ctx.restore(); | ||||
| 							return { | ||||
| 								datapoint: [s.percent, s.data], | ||||
| 								dataIndex: 0, | ||||
| 								series: s, | ||||
| 								seriesIndex: i | ||||
| 							}; | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					ctx.restore(); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		function onMouseMove(e) { | ||||
| 			triggerClickHoverEvent("plothover", e); | ||||
| 		} | ||||
|  | ||||
| 		function onClick(e) { | ||||
| 			triggerClickHoverEvent("plotclick", e); | ||||
| 		} | ||||
|  | ||||
| 		// trigger click or hover event (they send the same parameters so we share their code) | ||||
|  | ||||
| 		function triggerClickHoverEvent(eventname, e) { | ||||
|  | ||||
| 			var offset = plot.offset(); | ||||
| 			var canvasX = parseInt(e.pageX - offset.left); | ||||
| 			var canvasY =  parseInt(e.pageY - offset.top); | ||||
| 			var item = findNearbySlice(canvasX, canvasY); | ||||
|  | ||||
| 			if (options.grid.autoHighlight) { | ||||
|  | ||||
| 				// clear auto-highlights | ||||
|  | ||||
| 				for (var i = 0; i < highlights.length; ++i) { | ||||
| 					var h = highlights[i]; | ||||
| 					if (h.auto == eventname && !(item && h.series == item.series)) { | ||||
| 						unhighlight(h.series); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// highlight the slice | ||||
|  | ||||
| 			if (item) { | ||||
| 				highlight(item.series, eventname); | ||||
| 			} | ||||
|  | ||||
| 			// trigger any hover bind events | ||||
|  | ||||
| 			var pos = { pageX: e.pageX, pageY: e.pageY }; | ||||
| 			target.trigger(eventname, [pos, item]); | ||||
| 		} | ||||
|  | ||||
| 		function highlight(s, auto) { | ||||
| 			//if (typeof s == "number") { | ||||
| 			//	s = series[s]; | ||||
| 			//} | ||||
|  | ||||
| 			var i = indexOfHighlight(s); | ||||
|  | ||||
| 			if (i == -1) { | ||||
| 				highlights.push({ series: s, auto: auto }); | ||||
| 				plot.triggerRedrawOverlay(); | ||||
| 			} else if (!auto) { | ||||
| 				highlights[i].auto = false; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		function unhighlight(s) { | ||||
| 			if (s == null) { | ||||
| 				highlights = []; | ||||
| 				plot.triggerRedrawOverlay(); | ||||
| 			} | ||||
|  | ||||
| 			//if (typeof s == "number") { | ||||
| 			//	s = series[s]; | ||||
| 			//} | ||||
|  | ||||
| 			var i = indexOfHighlight(s); | ||||
|  | ||||
| 			if (i != -1) { | ||||
| 				highlights.splice(i, 1); | ||||
| 				plot.triggerRedrawOverlay(); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		function indexOfHighlight(s) { | ||||
| 			for (var i = 0; i < highlights.length; ++i) { | ||||
| 				var h = highlights[i]; | ||||
| 				if (h.series == s) | ||||
| 					return i; | ||||
| 			} | ||||
| 			return -1; | ||||
| 		} | ||||
|  | ||||
| 		function drawOverlay(plot, octx) { | ||||
|  | ||||
| 			var options = plot.getOptions(); | ||||
|  | ||||
| 			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; | ||||
|  | ||||
| 			octx.save(); | ||||
| 			octx.translate(centerLeft, centerTop); | ||||
| 			octx.scale(1, options.series.pie.tilt); | ||||
|  | ||||
| 			for (var i = 0; i < highlights.length; ++i) { | ||||
| 				drawHighlight(highlights[i].series); | ||||
| 			} | ||||
|  | ||||
| 			drawDonutHole(octx); | ||||
|  | ||||
| 			octx.restore(); | ||||
|  | ||||
| 			function drawHighlight(series) { | ||||
|  | ||||
| 				if (series.angle <= 0 || isNaN(series.angle)) { | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); | ||||
| 				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor | ||||
| 				octx.beginPath(); | ||||
| 				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { | ||||
| 					octx.moveTo(0, 0); // Center of the pie | ||||
| 				} | ||||
| 				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); | ||||
| 				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); | ||||
| 				octx.closePath(); | ||||
| 				octx.fill(); | ||||
| 			} | ||||
| 		} | ||||
| 	} // end init (plugin body) | ||||
|  | ||||
| 	// define pie specific options and their default values | ||||
|  | ||||
| 	var options = { | ||||
| 		series: { | ||||
| 			pie: { | ||||
| 				show: false, | ||||
| 				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) | ||||
| 				innerRadius: 0, /* for donut */ | ||||
| 				startAngle: 3/2, | ||||
| 				tilt: 1, | ||||
| 				shadow: { | ||||
| 					left: 5,	// shadow left offset | ||||
| 					top: 15,	// shadow top offset | ||||
| 					alpha: 0.02	// shadow alpha | ||||
| 				}, | ||||
| 				offset: { | ||||
| 					top: 0, | ||||
| 					left: "auto" | ||||
| 				}, | ||||
| 				stroke: { | ||||
| 					color: "#fff", | ||||
| 					width: 1 | ||||
| 				}, | ||||
| 				label: { | ||||
| 					show: "auto", | ||||
| 					formatter: function(label, slice) { | ||||
| 						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>"; | ||||
| 					},	// formatter function | ||||
| 					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) | ||||
| 					background: { | ||||
| 						color: null, | ||||
| 						opacity: 0 | ||||
| 					}, | ||||
| 					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow) | ||||
| 				}, | ||||
| 				combine: { | ||||
| 					threshold: -1,	// percentage at which to combine little slices into one larger slice | ||||
| 					color: null,	// color to give the new slice (auto-generated if null) | ||||
| 					label: "Other"	// label to give the new slice | ||||
| 				}, | ||||
| 				highlight: { | ||||
| 					//color: "#fff",		// will add this functionality once parseColor is available | ||||
| 					opacity: 0.5 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	$.plot.plugins.push({ | ||||
| 		init: init, | ||||
| 		options: options, | ||||
| 		name: "pie", | ||||
| 		version: "1.1" | ||||
| 	}); | ||||
|  | ||||
| })(jQuery); | ||||
							
								
								
									
										14
									
								
								template_static/lib/log.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								template_static/lib/log.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| (function($, block) { | ||||
| block.fn.log = function(config) { | ||||
|     this.$element.addClass('block log').append('<ul>'); | ||||
|  | ||||
|     this.actions(function(e, message){ | ||||
|         $ul = $('ul:first-child', this); | ||||
|         $ul.append('<li>'); | ||||
|         $ul.find("> li:last-child").text(message.text); | ||||
|         $(this).scrollTop(1000000); | ||||
|     }); | ||||
|  | ||||
|     return this.$element; | ||||
| }; | ||||
| })(jQuery, block); | ||||
							
								
								
									
										128
									
								
								template_static/lib/tweets.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								template_static/lib/tweets.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| (function($, block) { | ||||
|  | ||||
| // Entity formatters for use by tweet list | ||||
| var entity_formatters = { | ||||
|     'urls': function(e) { | ||||
|         return '<a href="' + e.url + '">' + e.display_url + '</a>'; | ||||
|     }, | ||||
|      | ||||
|     'user_mentions': function(e) { | ||||
|         return '<a href="https://twitter.com/'+e.screen_name+'">@'+e.screen_name+'</a>'; | ||||
|     }, | ||||
|  | ||||
|     'hashtags': function(e) { | ||||
|         return '<a href="https://twitter.com/hashtag/'+e.text+'?src=hash">#' +e.text+'</a>'; | ||||
|     }, | ||||
|  | ||||
|     'default': function(e) { | ||||
|         return '{ENTITY}'; | ||||
|     } | ||||
| }; | ||||
|  | ||||
| // processes entities for the given message and entity object | ||||
| var process_entities = function(message, entities) { | ||||
|     // short-circuit failure mode | ||||
|     if(typeof entities === 'undefined') { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     // build list of entities sorted on starting index | ||||
|     var es = []; | ||||
|  | ||||
|     $.each(entities, function(t, ts) { | ||||
|         $.each(ts, function(_, e) { | ||||
|             e['type'] = t; | ||||
|             es.push(e); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     es.sort(function(a,b) { | ||||
|         return a['indices'][0] - b['indices'][0]; | ||||
|     }); | ||||
|  | ||||
|     // process entities one-by-one in order of appearance | ||||
|     var marker = 0; | ||||
|     var result = ""; | ||||
|     for(var i in es) { | ||||
|         var e = es[i]; | ||||
|         var start = e['indices'][0]; | ||||
|         var stop = e['indices'][1]; | ||||
|  | ||||
|         //copy string content | ||||
|         result += message.substring(marker, start); | ||||
|  | ||||
|         //process entity (through formatter or no-op function) | ||||
|         var formatter = entity_formatters[e.type] | ||||
|                         || function(e) { return message.substring(start,stop) }; | ||||
|         result += formatter(e); | ||||
|  | ||||
|         // update marker location | ||||
|         marker = stop; | ||||
|     } | ||||
|  | ||||
|     // append tail of message | ||||
|     result += message.substring(marker, message.length); | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| block.fn.tweets = function(config) { | ||||
|     var options = $.extend({ | ||||
|         memory: 20 | ||||
|     }, config); | ||||
|  | ||||
|     // create the necessary HTML in the block container | ||||
|     this.$element.append('<ol class="tweet-list stream-items"></ol>'); | ||||
|  | ||||
|     // store list for later | ||||
|     var $list = this.$element.find('ol'); | ||||
|  | ||||
|  | ||||
|     // register default handler for handling tweet data | ||||
|     this.actions(function(e, tweet){ | ||||
|         var $item = $('<li class="stream-item"></li>'); | ||||
|  | ||||
|         var $tweet = $('<div class="tweet"></div>'); | ||||
|         var $content = $('<div class="content"></div>'); | ||||
|         var $header = $('<div class="stream-item-header"></div>'); | ||||
|  | ||||
|         // Build a tag image and header: | ||||
|         var $account = $('<a class="account-group"></a>'); | ||||
|         $account.attr("href", "http://twitter.com/" + tweet.user.screen_name); | ||||
|  | ||||
|         var $avatar = $("<img>").addClass("avatar"); | ||||
|         $avatar.attr("src", tweet.user.profile_image_url); | ||||
|         $account.append($avatar); | ||||
|         $account.append($('<strong class="fullname">' + tweet.user.name + '</strong>')); | ||||
|         $account.append($('<span> </span>')); | ||||
|         $account.append($('<span class="username"><s>@</s><b>' + tweet.user.screen_name + '</b></span>')); | ||||
|         $header.append($account); | ||||
|  | ||||
|         // Build timestamp: | ||||
|         var $time = $('<small class="time"></small>'); | ||||
|         $time.append($('<span>' + tweet.created_at + '</span>')); | ||||
|  | ||||
|         $header.append($time); | ||||
|         $content.append($header); | ||||
|  | ||||
|         // Build contents: | ||||
|         var text = process_entities(tweet.text, tweet.entities); | ||||
|         var $text = $('<p class="tweet-text">' + text + '</p>'); | ||||
|         $content.append($text); | ||||
|  | ||||
|         // Build outer structure of containing divs: | ||||
|         $tweet.append($content); | ||||
|         $item.append($tweet); | ||||
|          | ||||
|         // place new tweet in front of list  | ||||
|         $list.prepend($item); | ||||
|  | ||||
|         // remove stale tweets | ||||
|         if ($list.children().length > options.memory) { | ||||
|             $list.children().last().remove(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     return this.$element; | ||||
| }; | ||||
| })(jQuery, block); | ||||
							
								
								
									
										88
									
								
								template_static/lib/wordcloud.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								template_static/lib/wordcloud.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| (function($, block) { | ||||
|     // a simple wordcloud example | ||||
|     block.fn.wordcloud = function(config) { | ||||
|         var options = $.extend({ | ||||
|             filter_function : function(cat,val,max) { return true; }, | ||||
|             weight_function : function(cat,val,max) { return val; }, | ||||
|             options : {} | ||||
|         }, config); | ||||
|  | ||||
|         var $container = $(this.$element); | ||||
|  | ||||
|         // dict containing the labels and values | ||||
|         var worddata_dict = {}; | ||||
|         var dirty = false; | ||||
|  | ||||
|         var addword = function(label, value) { | ||||
|             if (worddata_dict.hasOwnProperty(label)) { | ||||
|                 worddata_dict[label] += value; | ||||
|             } else { | ||||
|                 worddata_dict[label] = value; | ||||
|             } | ||||
|             flag_dirty(); | ||||
|         } | ||||
|  | ||||
|         var setword = function(label, value) { | ||||
|             worddata_dict[label] = value; | ||||
|             flag_dirty(); | ||||
|         } | ||||
|  | ||||
|         var flag_dirty = function() { | ||||
|             dirty = true; | ||||
|         } | ||||
|  | ||||
|         var redraw = function() { | ||||
|             if(!dirty) { | ||||
|                 window.setTimeout(redraw, 500); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             var result = []; | ||||
|             var max = 0; | ||||
|  | ||||
|             for (var k in worddata_dict) { | ||||
|                 if (worddata_dict.hasOwnProperty(k)) { | ||||
|                         max = Math.max(max, worddata_dict[k]); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             for (var k in worddata_dict) { | ||||
|                 if (worddata_dict.hasOwnProperty(k)) { | ||||
|                     var val = worddata_dict[k]; | ||||
|                     if (options.filter_function(k,val,max)) { | ||||
|                         result.push({ | ||||
|                             text: k, | ||||
|                             weight: options.weight_function(k,val,max) | ||||
|                         }); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             $($container).empty().jQCloud(result,$.extend(options.options,{delayedMode: false})); | ||||
|  | ||||
|             dirty = false; | ||||
|             window.setTimeout(redraw, 500); | ||||
|         } | ||||
|  | ||||
|         var reset = function() { | ||||
|             worddata_dict = {}; | ||||
|         } | ||||
|  | ||||
|         this.actions({ | ||||
|             'set': function(e, message) { | ||||
|                 setword(message.value[0], message.value[1]); | ||||
|             }, | ||||
|             'add': function(e, message) { | ||||
|                 addword(message.value[0], message.value[1]); | ||||
|             }, | ||||
|             'reset': function(e, message) { | ||||
|                 reset(); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // start redraw loop | ||||
|         window.setTimeout(redraw, 500); | ||||
|  | ||||
|         // return element to allow further work | ||||
|         return this.$element; | ||||
|     } | ||||
| })(jQuery, block); | ||||
		Reference in New Issue
	
	Block a user