(function(window, $) {
function graph_log() {
// if (console)
// console.log(arguments);
}
var global = {};
function valid_date(date) {
if (typeof date !== 'string')
return false;
var segs = date.split('-');
if (segs.length !== 3)
return false;
if (segs[0].length !== 4 || segs[1].length !== 2 || segs[2].length !== 2 )
return false;
for (var i in segs)
if ( isNaN(segs[i]) )
return false;
return true;
}
function Graph(element) {
if (! (this instanceof Graph))
return new Graph(element);
if (typeof element === "string") {
this.id = element;
$element = this.$graph();
} else {
this.id = templater.__uuid__();
$outer = $(element)
$element = $outer.wrapInner('').children('graph-templater');
$element
.attr('graph-uuid', this.id);
}
this.definitions = {
"indicator": null,
"site": null,
"watercourse": null,
"program": null,
"start": null,
"stop": null,
"aggregation_calculator": null,
"aggregation_interval": null,
"integrity_level": null,
"error": null,
}
this.scoper = DataScoper(this.definitions);
this.templater = global.templater.subTemplater($element, this.scoper.data);
global.graphs[this.id] = this;
this.templater.update('id', this.id);
var scopes = this.scoper.scopes();
var _this = this;
for (var i in scopes) {
this.scoper.subscribe( scopes[i], function(data, scope) {
graph_log(_this.name, ': Updating data', scope, data, _this);
_this.templater.update(scope, data);
});
}
this.global_data = global.scoper.data;
this.data = {
"columns": {},
"data": {},
}
this.listeners = {};
this.name = null;
this.init();
}
Graph.prototype = {
"$container": function () {
return this.$graph().parent();
},
"$graph": function () {
return $('[graph-uuid="'+this.id+'"]');
},
"init": function() {
this.name = this.parseAttr('name');
this.table = this.parseAttr('table', false);
this.setAttr( 'site', this.parseAttr('site') );
this.setAttr( 'indicator', this.parseAttr('indicator') );
var calculator = this.parseAttr('calculator', false);
var match = false;
for (var i in this.global_data.calculators) {
if (this.global_data.calculators[i].id == calculator)
match = true
}
if (match) {
this.scoper.aggregation_calculator = calculator;
}
else {
this.setListener( 'aggregation_calculator', calculator, 'change', function(event, element, value) {
this.scoper['aggregation_calculator'] = value;
this.update();
});
}
var interval = this.parseAttr('interval', false);
var match = false;
for (var i in this.global_data.intervals) {
if (this.global_data.intervals[i].id == interval)
match = true
}
if (match) {
this.scoper.aggregation_interval = interval;
}
else {
this.setListener( 'aggregation_interval', interval, 'change', function(event, element, value) {
this.scoper['aggregation_interval'] = value;
this.update();
});
}
var ilevel = this.parseAttr('integrity_level', false);
var match = false;
for (var i in this.global_data.integrity_levels) {
if (this.global_data.integrity_levels[i].id == ilevel)
match = true
}
if (match) {
this.scoper.integrity_level = ilevel;
}
else {
this.setListener( 'integrity_level', ilevel, 'change', function(event, element, value) {
this.scoper['integrity_level'] = value;
this.update();
});
}
this.setAdder('add-site', function(form, input_name) {
var $input = $(form)
.find('[name="'+(input_name || 'site')+'"]');
var value = $input.val();
this.getGraphData( value, this.indicator, true );
});
this.setAdder('add-indicator', function(form, input_name) {
var $input = $(form)
.find('[name="'+(input_name || 'indicator')+'"]');
var value = $input.val();
this.getGraphData( this.site, value, true );
});
var ts_attr = this.parseAttr('timespan', false);
graph_log(ts_attr);
$('body').on('keyup', 'form[name="'+ts_attr+'"] input', function(e) {
var val = $(this).val();
if (valid_date(val))
$(this).parent().addClass('has-success').removeClass('has-error');
else
$(this).parent().addClass('has-error').removeClass('has-success');
});
this.setAdder('timespan', function(form, input_name) {
var $start = $(form).find('[name="start"]');
var $stop = $(form).find('[name="stop"]');
var start = $start.val();
var stop = $stop.val()
if (start) {
if (valid_date(start))
this.scoper.start = start;;
}
if (stop) {
if (valid_date(stop))
this.scoper.stop = stop;
}
this.getGraphData( this.site, this.indicator );
});
this.templater.update('name', "Graph: " + this.name);
this.update();
},
"update": function() {
if ( ! (isNaN(this.site) || isNaN(this.indicator) || this.site === '' || this.indicator === '') )
this.getGraphData( this.site, this.indicator );
},
"parseAttr": function(attr, required) {
var str = this.$container().attr(attr);
if (str === undefined) {
if (required === false)
return false;
else
throw new Error("Graph requires attribute '"+attr+"'");
}
var parent_data = global.templater.getParentData(this.$graph());
var parent_elem = global.templater.getParentElement(this.$graph());
var $parent = global.templater.getParentData(parent_elem);
var value = global.templater.renderString( str, parent_data, undefined, $parent );
return value;
},
"updateTemplateData": function(key) {
if (isNaN(this[key])) {
return;
}
switch (key) {
case 'site':
this.scoper['site'] = this.global_data.sites[this[key]];
var wid = this.scoper.data['site'].watercourse_id;
this.scoper['watercourse'] = this.global_data.watercourses[wid];
var pid = this.scoper.data['site'].program_id;
this.scoper['program'] = this.global_data.programs[pid];
break;
case 'indicator':
this.scoper['indicator'] = this.global_data.indicators[this[key]];
break;
}
},
"setAttr": function(key, value) {
if (isNaN(value)) {
this.setListener( key, value, 'change', function(event, element, value) {
this.setAttr( key, value );
});
}
else {
this[key] = parseInt( value );
this.updateTemplateData( key )
}
this.update();
},
"setAdder": function(attr, callback) {
var add_attr = this.parseAttr(attr, false);
if (! add_attr)
return;
var fi = add_attr.split(':');
var form_name = fi[0];
var input_name = fi[1];
var _this = this;
this.setListener( form_name, form_name, 'submit', function(event, form, value) {
callback.call(_this, form, input_name);
});
},
"setListener": function( key, name, event, callback ) {
if (this.listeners[key] !== undefined)
return;
var selector = 'select[name="'+name+'"], input[name="'+name+'"], textarea[name="'+name+'"], form[name="'+name+'"]';
var event = event ? event : 'change';
var _this = this;
$('body').on(event, selector, function(e) {
e.preventDefault();
if (callback)
callback.call(_this, e, this, $(this).val() );
});
this.listeners[key] = selector;
this.setAttr( key, $(selector).val() );
},
"getGraphData": function( site, indicator, add ) {
if ( isNaN(site) || isNaN(indicator)
|| site == '' || indicator == '' )
console.error("Cannot fetch data for undefined site or indicator (site: "+site+', indicator: '+indicator+')');
else {
var _this = this;
var url = '/api/sites/'+site+'/graph/'+indicator;
var params = {};
if (this.scoper.data.start)
params['start'] = this.scoper.data.start;
if (this.scoper.data.stop)
params['stop'] = this.scoper.data.stop;
if (this.scoper.data.aggregation_calculator)
params['aggregation_calculator'] = this.scoper.data.aggregation_calculator;
if (this.scoper.data.aggregation_interval)
params['aggregation_interval'] = this.scoper.data.aggregation_interval;
if (this.scoper.data.integrity_level)
params['integrity_level'] = this.scoper.data.integrity_level;
graph_log('params', params);
if (window.RWAPI !== undefined)
url = window.RWAPI+url;
$.ajax({
url: url,
dataType: "jsonp",
data: params,
success: function( data ) {
if(Object.keys(data).length === 0) {
_this.scoper['error'] = 'No results for "'+_this.global_data.indicators[indicator].name+'" at site "'+_this.global_data.sites[site].name+'"';
return;
}
_this.scoper['error'] = null;
if (! data)
throw new Error("No data returned: type ("+(typeof data)+") for url "+this.url);
if (add === true)
_this.addGraphData(data, this.url);
else
_this.updateGraphData(data, this.url);
},
error: function(a,b,c) {
graph_log(a,b,c);
}
});
}
},
"updateGraphData": function(data, url) {
this.data.columns = {};
this.data.data = {};
this.data.medians = {};
this.addGraphData(data, url);
},
"addGraphData": function(data, url) {
if (data.name === undefined || data.graph === undefined)
graph_log("Incomplete data: (keys) ["+Object.keys(data).join()+"] for url "+url);
var symbol = data.indicator.symbol ? ' ('+ data.indicator.symbol+')' : ''
this.data.columns[data.name] = data.watercourse.name+': '+data.site.name+' - '+data.indicator.name + symbol;
this.data.data[data.name] = data.graph;
this.data.medians[data.name] = data.median;
this.scoper.start = data.start;
this.scoper.stop = data.stop;
this.scoper.aggregation_calculator = data.aggregation;
this.scoper.aggregation_interval = data.interval;
this.scoper.integrity_level = data.integrity_level;
this.buildGraph();
},
"fillTable": function(columns, medians, data) {
var data = data.sort(function(a,b) { return a[0]-b[0]; });
var $table = $(this.table);
var $thead = $('');
var $tbody = $('
');
var $tr = $('
');
var colors = [
'#2F63CF', // blue
'#DE3700', // red
'#FF9A00', // orange
'#009802', // green
'#9B009B', // purple
'#0098C8', // light blue
'#DF4176', // pink,
'#64AC00', // light green
'#BA2C29', // maroon
'#2E6297', // navy
'#9A409B', // light purple
'#12AB99', // teal
]
$tr.append(' | ');
for (var i=1; i'+c+'');
}
$thead.append($tr);
var $tr = $('
');
$tr.append('Median | ');
for (var i=1; i'+m+'');
}
$thead.append($tr);
graph_log(columns, data);
for (var d in data) {
var datum = data[d];
var $tr = $('
');
var match = false;
var date = datum[0].toISOString().slice(0,10);
$tr.append(''+date+' | ');
for (var i=1; i'+value+'');
}
if (!match)
continue;
$tbody.append($tr);
}
$table.empty().append($thead, $tbody);
},
"buildGraph": function() {
console.log(this.data);
var graph_data = JSON.parse( JSON.stringify( this.data ) );
var columns = [];
for (var i in graph_data.columns) {
columns.push( graph_data.columns[i] );
}
var medians = [null];
for (var i in graph_data.medians) {
medians.push( graph_data.medians[i] );
}
console.log(medians);
var data = graph_data.data;
data = Object.keys( graph_data.data ).map(function(i) {
return data[i];
});
var $container = this.$graph().find('graph-container').wrapInner('').children('div');
$container.css({
'width': '100%',
'height': '100%',
});
this.drawChart( $container, columns, medians, data);
},
"drawChart": function( $element, columns, medians, data ) {
graph_log('drawChart()', columns, data);
columns.unshift("Date")
var rows = {}
var minDate = new Date();
var maxDate = new Date();
var minValue = null;
var maxValue = null;
$.each( data, function( i, site ){
$.each( site, function( di, d ) {
var k = new Date(d.x)
k.setTime(k.getTime()+(k.getTimezoneOffset()*60000));
var v = d.y;
if (minDate === null || k < minDate)
minDate = k;
if (maxDate === null || k > maxDate)
maxDate = k;
if (minValue === null || v < minValue)
minValue = v;
if (maxValue === null || v > maxValue)
maxValue = v;
if (rows[k] === undefined) {
rows[k] = [k];
rows[k][i+1] = v;
}
else {
rows[k][i+1] = v;
}
});
});
var minYear = minDate.getFullYear();
var maxYear = maxDate.getFullYear();
graph_log(minYear, maxYear+1, minValue, maxValue);
for (var i=minYear; i <= (maxYear); i++) {
var k = new Date((i)+'-04-01');
rows[k] = [k];
var k = new Date((i+1)+'-04-01');
rows[k] = [k];
}
var result = [];
for (var i in rows) {
var points = rows[i];
var d = new Date( points[0].toString() );
d.setHours(0,0,1);
var tmp = [d]
for (var i=0; i<=data.length; i++) {
if (points[i] === undefined)
points[i] = null;
if (tmp[i] === undefined)
tmp[i] = null;
}
result.push(tmp);
result.push(points);
}
graph_log("ROWS AND RESULTS", rows, result);
if (this.table)
this.fillTable(columns, medians, result);
var data = new google.visualization.DataTable();
var symbol_regex = /\(([^)]+)\)/;
var indicator_regex = /\-\ ([^)]+)/;
for (var c in columns) {
if (c === "0")
data.addColumn('date', 'Date');
else {
var matches = symbol_regex.exec(columns[c]) || indicator_regex.exec(columns[c])
data.addColumn('number', matches[1]);
}
}
data.addRows(result);
graph_log('Chart data', data);
// var element = $element.clone().empty().get(0);
var element = $element.get(0);
var chart = new google.visualization.AnnotationChart(element);
google.visualization.events.addListener(chart, 'ready', function() {
$(element).removeClass('container');
});
// $element.before(element).remove();
var suffix = columns.length < 3
? this.scoper.data.indicator.symbol
: '';
var diff = maxValue-minValue;
var margin = diff*0.25;
var min = minValue-margin;
var max = maxValue+margin;
graph_log(min, max);
chart.draw(data, {
title: 'Riverwatch Graph',
displayAnnotations: true,
// displayAnnotationsFilter: true,
displayZoomButtons: true,
displayExactValues: true,
allValuesSuffix: ' '+suffix,
fill: 0,
thickness: 4,
legendPosition: 'newRow',
min: min,
max: max,
scaleColumns: [1]
});
}
};
function run_graphs() {
var $graphs = $('body').find('graph');
$graphs.each(function(i) {
var graph = Graph(this);
});
}
var definitions = {
"programs": "/api/programs",
"watercourses": "/api/watercourses",
"sites": "/api/sites",
"indicators": "/api/indicators",
"sciences": "/api/sciences",
"integrity_levels": "/api/integrity_levels",
"calculators": "/api/aggregation/calculators",
"intervals": "/api/aggregation/intervals"
};
var scoper = DataScoper(definitions);
var templater = Templater(scoper.data, undefined, false);
templater.ignoreElement('graph');
global.templater = templater;
global.scoper = scoper;
global.graphs = {};
function benchmark_log() {
// if (console)
// console.log(arguments);
}
function benchmark(name, fn, $this) {
var start = new Date();
fn.call($this);
var end = new Date();
benchmark_log(name, end-start);
return end;
}
$.wait( Object.keys(definitions).length, function() {
graph_log('Wait callback', global.scoper.data);
benchmark('Binding', function() {
scoper.getAll(function(scope, data) {
benchmark('Binding: '+scope, function() {
templater.updateData(scope, data);
})
});
});
benchmark('Graphs', function() {
run_graphs();
});
benchmark('DOM', function() {
templater.updateDOM();
});
$('input, select, textarea')
.filter('[default-value]').each(function(i) {
updateInputValue( this );
});
});
scoper.getAll(function(scope, data) {
$.wait();
});
})(window, jQuery);