Changed tweets, added eca

This commit is contained in:
2022-10-25 12:15:41 +02:00
parent 7597d7648a
commit 9860bee497
71 changed files with 7476 additions and 2036 deletions

View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<title>Neca Test</title>
<link rel="stylesheet" href="/style/layout.css"/>
<link rel="stylesheet" href="/style/theme.css"/>
<script src="/lib/jquery-2.1.1.min.js"></script>
<script src="/lib/jquery.flot.min.js"></script>
<script src="/lib/core.js"></script>
<script src="/lib/charts.js"></script>
<script src="/lib/log.js"></script>
</head>
<body class="container_12">
<h1>ECA Dashboard Template</h1>
<div class="grid_6 vert_4">
<p>This is the dashboard template file. The easiest way to get started is to think up a simple name (let's say we take 'dashboard'). Now copy <code>template.py</code> to <code>{name}.py</code> start a new module (so that's <code>dashboard.py</code>) and copy <code>template_static</code> to <code>{name}_static</code>.
<p>Now you can run the new project with: <pre>python neca.py -s {name}.py</pre>
<p>Further documentation on the ECA system can be found at <a href="https://github.com/utwente-db/eca/wiki">github.com/utwente-db/eca/wiki</a>, and demos can be found in the <code>demos/</code> directory.
</div>
<div class="grid_6 vert_4">
<p>In the sample <code>template.py</code> (which comes with the dashboard you're looking at right now), you will find the rules that power this example.
<p>Rules are written in <a href="https://www.python.org/">Python</a> and work as follows:
<pre>@event("foo")
def action(context, event):
print("Event " + event.name + "!")
</pre>
The <code>@event</code> part tells the system to fire the action whenever the event 'foo' occurs. The <code>def action(context, event):</code> part defines a new action that takes two arguments: the context and the event. The rest of the code is the action body.
</div>
<div class="clear"></div>
<div class="grid_4">
<p>The graph to the right is continuously filled with data generated by the rules.
<p>In <code>template.py</code> you can see that an event called 'sample' is fired again and again to create new data points for the graph.
<p>These points are then sent to the browser with:
<pre>emit('sample',{
'action': 'add',
'value': sample
})</pre>
</div>
<div id="graph" class="grid_8 vert_4"></div>
<script>
// create a rolling chart block
block('#graph').rolling_chart({
memory: 150,
chart: {
yaxis: {
min: -100,
max: 100
},
xaxis: {
show: false
}
}
});
// connect sample event to graph
events.connect('sample', '#graph');
</script>
</body>
</html>

View 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
View 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);

View 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);

View 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);

View File

File diff suppressed because one or more lines are too long

View 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);

View File

File diff suppressed because one or more lines are too long

View 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);

View 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);

View 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>&nbsp;</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);

View 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);

View File

@@ -0,0 +1,374 @@
/*
Variable Grid System.
Learn more ~ http://www.spry-soft.com/grids/
Based on 960 Grid System - http://960.gs/
Licensed under GPL and MIT.
*/
/*
Forces backgrounds to span full width,
even if there is horizontal scrolling.
Increase this if your layout is wider.
Note: IE6 works fine without this fix.
*/
body {
min-width: 960px;
}
/* Containers
----------------------------------------------------------------------------------------------------*/
.container_12 {
margin-left: auto;
margin-right: auto;
width: 960px;
}
/* Grid >> Global
----------------------------------------------------------------------------------------------------*/
.grid_1,
.grid_2,
.grid_3,
.grid_4,
.grid_5,
.grid_6,
.grid_7,
.grid_8,
.grid_9,
.grid_10,
.grid_11,
.grid_12 {
display:inline;
float: left;
position: relative;
margin-left: 10px;
margin-right: 10px;
}
.push_1, .pull_1,
.push_2, .pull_2,
.push_3, .pull_3,
.push_4, .pull_4,
.push_5, .pull_5,
.push_6, .pull_6,
.push_7, .pull_7,
.push_8, .pull_8,
.push_9, .pull_9,
.push_10, .pull_10,
.push_11, .pull_11,
.push_12, .pull_12 {
position:relative;
}
/* Grid >> Children (Alpha ~ First, Omega ~ Last)
----------------------------------------------------------------------------------------------------*/
.alpha {
margin-left: 0;
}
.omega {
margin-right: 0;
}
/* Grid >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .grid_1 {
width:60px;
}
.container_12 .grid_2 {
width:140px;
}
.container_12 .grid_3 {
width:220px;
}
.container_12 .grid_4 {
width:300px;
}
.container_12 .grid_5 {
width:380px;
}
.container_12 .grid_6 {
width:460px;
}
.container_12 .grid_7 {
width:540px;
}
.container_12 .grid_8 {
width:620px;
}
.container_12 .grid_9 {
width:700px;
}
.container_12 .grid_10 {
width:780px;
}
.container_12 .grid_11 {
width:860px;
}
.container_12 .grid_12 {
width:940px;
}
/* Prefix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .prefix_1 {
padding-left:80px;
}
.container_12 .prefix_2 {
padding-left:160px;
}
.container_12 .prefix_3 {
padding-left:240px;
}
.container_12 .prefix_4 {
padding-left:320px;
}
.container_12 .prefix_5 {
padding-left:400px;
}
.container_12 .prefix_6 {
padding-left:480px;
}
.container_12 .prefix_7 {
padding-left:560px;
}
.container_12 .prefix_8 {
padding-left:640px;
}
.container_12 .prefix_9 {
padding-left:720px;
}
.container_12 .prefix_10 {
padding-left:800px;
}
.container_12 .prefix_11 {
padding-left:880px;
}
/* Suffix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .suffix_1 {
padding-right:80px;
}
.container_12 .suffix_2 {
padding-right:160px;
}
.container_12 .suffix_3 {
padding-right:240px;
}
.container_12 .suffix_4 {
padding-right:320px;
}
.container_12 .suffix_5 {
padding-right:400px;
}
.container_12 .suffix_6 {
padding-right:480px;
}
.container_12 .suffix_7 {
padding-right:560px;
}
.container_12 .suffix_8 {
padding-right:640px;
}
.container_12 .suffix_9 {
padding-right:720px;
}
.container_12 .suffix_10 {
padding-right:800px;
}
.container_12 .suffix_11 {
padding-right:880px;
}
/* Push Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .push_1 {
left:80px;
}
.container_12 .push_2 {
left:160px;
}
.container_12 .push_3 {
left:240px;
}
.container_12 .push_4 {
left:320px;
}
.container_12 .push_5 {
left:400px;
}
.container_12 .push_6 {
left:480px;
}
.container_12 .push_7 {
left:560px;
}
.container_12 .push_8 {
left:640px;
}
.container_12 .push_9 {
left:720px;
}
.container_12 .push_10 {
left:800px;
}
.container_12 .push_11 {
left:880px;
}
/* Pull Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .pull_1 {
left:-80px;
}
.container_12 .pull_2 {
left:-160px;
}
.container_12 .pull_3 {
left:-240px;
}
.container_12 .pull_4 {
left:-320px;
}
.container_12 .pull_5 {
left:-400px;
}
.container_12 .pull_6 {
left:-480px;
}
.container_12 .pull_7 {
left:-560px;
}
.container_12 .pull_8 {
left:-640px;
}
.container_12 .pull_9 {
left:-720px;
}
.container_12 .pull_10 {
left:-800px;
}
.container_12 .pull_11 {
left:-880px;
}
/* `Clear Floated Elements
----------------------------------------------------------------------------------------------------*/
/* http://sonspring.com/journal/clearing-floats */
.clear {
clear: both;
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
/* http://www.yuiblog.com/blog/2010/09/27/clearfix-reloaded-overflowhidden-demystified */
.clearfix:before,
.clearfix:after {
content: '\0020';
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
.clearfix:after {
clear: both;
}
/*
The following zoom:1 rule is specifically for IE6 + IE7.
Move to separate stylesheet if invalid CSS is a problem.
*/
.clearfix {
zoom: 1;
}

View File

@@ -0,0 +1,69 @@
/*
** Base layout:
** Grid layout + vertical sizing classes sized to match
*/
/* Grid layout based on (http://960.gs/) */
@import url(grid.css);
/* Vertical classes */
.grid_1, .vert_1,
.grid_2, .vert_2,
.grid_3, .vert_3,
.grid_4, .vert_4,
.grid_5, .vert_5,
.grid_6, .vert_6,
.grid_7, .vert_7,
.grid_8, .vert_8,
.grid_9, .vert_9,
.grid_10, .vert_10,
.grid_11, .vert_11,
.grid_12, .vert_12 {
margin-top: 10px;
margin-bottom: 10px;
}
.container_12 .vert_1 { height:60px; }
.container_12 .vert_2 { height:140px; }
.container_12 .vert_3 { height:220px; }
.container_12 .vert_4 { height:300px; }
.container_12 .vert_5 { height:380px; }
.container_12 .vert_6 { height:460px; }
.container_12 .vert_7 { height:540px; }
.container_12 .vert_8 { height:620px; }
.container_12 .vert_9 { height:700px; }
.container_12 .vert_10 { height:780px; }
.container_12 .vert_11 { height:860px; }
.container_12 .vert_12 { height:940px; }
/* Layout details */
p:first-child {
margin-top: 0;
}
/* Log block */
.block.log {
overflow: auto;
}
/* Tweets block */
.tweet-list * {
margin: 0;
padding: 0;
}
.tweet-list.stream-items {
margin: 0;
padding: 0;
overflow: auto;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,160 @@
/* Basic style & theme*/
body {
font-family: sans-serif;
}
p {
text-align: justify;
}
code {
background-color: #eee;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0 0.2em;
}
pre {
padding: 0.5em 1.5em;
background-color: #eee;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
/* Devevlopment helpers */
.debug_red { background-color: rgba(255,0,0,0.5); }
.debug_green { background-color: rgba(0,255,0,0.5); }
.debug_blue { background-color: rgba(0,0,255,0.5); }
/* Tweets block */
.tweet-list.stream-items {
position: relative;
background-color: #fff;
list-style: none;
color: #333;
font-size: 14px;
line-height: 18px;
font-family: arial, sans-serif;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.tweet-list .stream-item {
background: #fff;
background-clip: padding-box;
}
.tweet-list .stream-item:hover {
background-color: #eee;
}
.tweet-list li.stream-item {
line-height: inherit
}
.tweet-list .tweet {
position: relative;
min-height: 51px;
padding: 9px 12px;
}
.tweet-list .stream-item + .stream-item {
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.tweet-list .tweet p {
word-wrap: break-word
}
.tweet-list .tweet .details {
display: inline-block;
margin-right: 2px
}
.tweet-list .tweet .context a {
color: #999
}
.tweet-list .stream-item .content {
margin-left: 58px
}
.tweet-list .stream-item-header .avatar {
float: left;
margin-top: 3px;
margin-left: -58px
}
.tweet-list .account-group {
color: #999
}
.tweet-list a {
color: #0084b4;
text-decoration: none
}
.tweet-list a:focus {
outline: 0
}
.tweet-list a:hover,
.tweet-list a:focus {
color: #0084b4;
text-decoration: underline
}
.tweet-list a.account-group:hover,
.tweet-list a.account-group:focus {
text-decoration: none;
}
.tweet-list a.account-group:hover .fullname,
.tweet-list a.account-group:focus .fullname {
text-decoration: underline;
}
.tweet-list .avatar {
width: 48px;
height: 48px;
border-radius: 5px;
-moz-force-broken-image-icon: 1
}
.tweet-list .fullname {
font-weight: bold;
color: #333;
}
.tweet-list .username {
font-size: 12px;
color: #999
}
.tweet-list .username s {
color: #bbb
}
.tweet-list s {
text-decoration: none
}
.tweet-list b {
font-weight: normal
}
.tweet-list .tweet .time {
position: relative;
float: right;
margin-top: 1px;
color: #bbb
}
.tweet-list .tweet-timestamp {
color: #999
}
.tweet-list .tweet .tweet-text {
white-space: pre-wrap
}

View File

@@ -0,0 +1,49 @@
/* fonts */
div.jqcloud {
font-family: "Helvetica", "Arial", sans-serif;
font-size: 10px;
line-height: normal;
}
div.jqcloud a {
font-size: inherit;
text-decoration: none;
}
div.jqcloud span.w10 { font-size: 550%; }
div.jqcloud span.w9 { font-size: 500%; }
div.jqcloud span.w8 { font-size: 450%; }
div.jqcloud span.w7 { font-size: 400%; }
div.jqcloud span.w6 { font-size: 350%; }
div.jqcloud span.w5 { font-size: 300%; }
div.jqcloud span.w4 { font-size: 250%; }
div.jqcloud span.w3 { font-size: 200%; }
div.jqcloud span.w2 { font-size: 150%; }
div.jqcloud span.w1 { font-size: 100%; }
/* colors */
div.jqcloud { color: #09f; }
div.jqcloud a { color: inherit; }
div.jqcloud a:hover { color: #0df; }
div.jqcloud a:hover { color: #0cf; }
div.jqcloud span.w10 { color: #0cf; }
div.jqcloud span.w9 { color: #0cf; }
div.jqcloud span.w8 { color: #0cf; }
div.jqcloud span.w7 { color: #39d; }
div.jqcloud span.w6 { color: #90c5f0; }
div.jqcloud span.w5 { color: #90a0dd; }
div.jqcloud span.w4 { color: #90c5f0; }
div.jqcloud span.w3 { color: #a0ddff; }
div.jqcloud span.w2 { color: #99ccee; }
div.jqcloud span.w1 { color: #aab5f0; }
/* layout */
div.jqcloud {
overflow: hidden;
position: relative;
}
div.jqcloud span { padding: 0; }