<commit_before>/* Main code */

/* RequireJS configurations */
requirejs.config({
	shim: {
		'bootstrap':{
			deps: ['jquery']
		},
		'showdown':{
			deps: [],
			exports: 'Showdown'
		}
	}
});

var INFODESK_GLOBAL = {};

/* Pop error message */
function add_error(args, jquery) {
	//alert('error: '+ JSON.stringify(args));
	var data, dialog;
	if(args && (typeof args === 'object')) {
		if(args.error) {
			args.title = args.error;
			delete args.error;
		}
		if(args.title || args.desc) {
			data = args;
		} else {
			data = {'desc': JSON.stringify(args)};
		}
	} else {
		data = {'title':''+args};
	}
	if(!data.date) data.date = new Date();
	function do_jquery(jquery) {
		dialog = jquery('#elements .error_dialog').clone();
		dialog.find('.date').text(data.date);
		dialog.find('.title').text(data.title);
		if(data.desc && ((data.desc+'').length > 0) ) {
			dialog.find('.desc').text(data.desc);
		} else {
			dialog.find('.desc_container').hide();
		}
		dialog.prependTo('#notifications');
		dialog.alert();
	}
	if(jquery) {
		do_jquery(jquery);
	} else {
		require(["jquery"], function(jquery) {
			do_jquery(jquery);
		}, function(err) {
			alert('Error: ' . err);
		});
	}
};

/* Post message to server */
function remove_msg(log_id, success_fn) {
		var jqxhr;
		require(["jquery"], function(jquery) {
			jqxhr = jquery.post('backend.php', {'remove_msg':log_id});
		}, function(err) { add_error(JSON.stringify(err)); });
		jqxhr.complete(function(response) {
			try {
				if(response && response.status && (200 === response.status) && (response.responseText.substr(0, 2) === 'OK') ) {
					success_fn();
				} else if(response && (response.status !== undefined)) {
					add_error({'title':'Connection failed with #' + response.status, 'desc':response.responseText});
				} else {
					add_error('Connection failed');
				}
			} catch(e) {
				add_error('Connection failed');
			}
		});
}

/* Post message to server */
function post_msg(args) {
		//alert('In post_msg() with args=' + JSON.stringify(args) );
		var args = args || {};
		var msg = (args && (typeof args === 'object') && args.msg) ? ''+args.msg : '';
		if( (msg.length === 0) || (msg.length >= 1024) ) {
			return;
		}
		var jqxhr;
		require(["jquery"], function(jquery) {
			jqxhr = jquery.post('backend.php', {'send_msg':'1', 'msg':''+msg});
		}, function(err) { add_error(JSON.stringify(err)); });
		jqxhr.complete(function(response) {
			try {
				if(response && response.status && (200 === response.status) && (response.responseText.substr(0, 2) === 'OK') ) {
					require(["jquery"], function(jquery) {
						jquery("#control_form .msg_field").val('');
					}, function(err) { add_error(JSON.stringify(err)); });
					update_events();
				} else if(response && (response.status !== undefined)) {
					add_error({'title':'Connection failed with #' + response.status, 'desc':response.responseText});
				} else {
					add_error('Connection failed');
				}
			} catch(e) {
				add_error('Connection failed');
			}
		});
	/*
	require(["jquery"], function(jquery) {
	}, function(err) {
		add_error({'title':'Connection failed', 'desc':JSON.stringify(err)});
	});
	*/
}

/* Post message to server */
function post_msg_form() {
	var msg;
	require(["jquery"], function(jquery) {
		msg = jquery('#control_form').find('.msg_field').val();
	}, function(err) {
		add_error({'title':'Clearing form failed', 'desc':JSON.stringify(err)});
	});
	//alert('Calling post_msg() with msg=' + msg);
	post_msg({'msg':msg});
	return false;
}

/* */
function change_search_string(q) {

	if(INFODESK_GLOBAL.updating === true) { return; }

	//alert(hashtag);
	INFODESK_GLOBAL.search_string = ''+q;
	require(["jquery"], function(jquery) {
		jquery('#events .events-header').empty();
		jquery('#events .events-body').empty();
		if(q !== '') {
			var header = jquery('
').html( 'Results for '+format_msg(jquery, ''+q) + ' ' );
			var link = jquery('').html("Close ×").click(function(){ change_search_string(''); });
			link.appendTo(header);
			header.appendTo('#events .events-header');
		}
		jquery('#search_field').val('');
	}, function(err) { add_error(JSON.stringify(err)); });
	INFODESK_GLOBAL.last_id = 0;
	update_events();
}

/* */
function format_msg(jquery, msg) {
	msg = jquery('
').text(msg).html();

	// Format hashtags
	msg = msg.replace(/#([a-zA-Z0-9\.]+)/g, function($0, $1) {
		var h = (''+$1).toLowerCase();
		var div = jquery('
');
		var a = jquery('').text('#'+$1);
		a.appendTo(div);
		return div.html();
	});

	return msg;
}

/* */
function update_events() {
	var next_id, hashtag;
	if(INFODESK_GLOBAL.updating === true) { return; }
	INFODESK_GLOBAL.updating = true;
	next_id = INFODESK_GLOBAL.last_id+1;
	require(["jquery", "moment"], function(jquery, moment) {
		var options = {'msgs':'1', 'start':''+next_id};
		if(INFODESK_GLOBAL.search_string && (INFODESK_GLOBAL.search_string !== '')) {
			options.q = INFODESK_GLOBAL.search_string;
		}
		var jqxhr = jquery.get('backend.php', options, function(data) {
			var response = JSON.parse(data),
			    events = response.events,
			    server_time = response.time;

			//alert('got events: ' + events.length);
			for(i in events) if(events.hasOwnProperty(i)) {
				(function() {
					var event = events[i],
					    id = parseInt(event.log_id, 10),
					    seconds_since = server_time - event.updated,
					    updated = new Date();

					updated.setTime( event.updated*1000 );

					if(id > INFODESK_GLOBAL.last_id) {
						INFODESK_GLOBAL.last_id = id;
					}

					var div = jquery('#elements .event_container').clone();
					div.find('.log_id').text(''+event.log_id);
					div.find('.date').text( moment(updated).format("HH:mm:ss") );
					div.find('.msg').html( format_msg(jquery, event.msg) );

					var close = div.find('.close');
					if(seconds_since < 5*60) {
						//alert("seconds_since = " + seconds_since);
						close.show();
						setTimeout(function() {
							close.hide();
						}, (5*60 - seconds_since)*1000);
					} else {
						close.hide();
					}

					close.click(function() {
						remove_msg(event.log_id, function() {
							div.remove();
						});
						return false;
					});

					div.prependTo('#events .events-body');
				}());
			}
			INFODESK_GLOBAL.updating = false;
		});
		jqxhr.complete(function(data, status) {
			if(status === "success") return;
			if(data && data.responseText) {
				try {
					var obj = JSON.parse(data.responseText);
					if(obj && obj.error) {
						add_error(obj.error, jquery);
						return;
					}
					add_error(obj);
				} catch(e) {
					add_error(data);
				}
			} else {
				add_error(status + ' with ' + data);
			}
		});
	}, function(err) { add_error(JSON.stringify(err)); });
}

/* */
function update_clock() {
	function f(d) { return ((''+d).length===1) ? '0'+d : ''+d; }
	require(["jquery"], function(jquery) {
		var now = new Date();
		jquery('#clock').val( f(now.getHours()) + ':' + f(now.getMinutes()) );
	});
}

/* */
INFODESK_GLOBAL.timer = undefined;
INFODESK_GLOBAL.updating = false;
INFODESK_GLOBAL.last_id = 0;
function update_events_timer() {
	update_events();
	//update_clock();
	setTimeout('update_events_timer()', 1000);
}

/* Init everything at onLoad event */
window.onload = function() {
	require(["bootstrap"], function(b) {});

	// TODO: Setup simple clock on control form

	// TODO: Setup previous event history
	// TODO: Start fetching new events
	//update_events();
	update_events_timer();

	//
	require(['jquery'], function(jquery) {

		// Setup ajax calls
		jquery.ajaxSetup({cache:false});

		// Setup search form
		jquery('.form-search').submit(function() {
			var q = jquery('#search_field').val();
			change_search_string(q);
			return false;
		});

		// Setup about modal's body
		require(['showdown'], function(Showdown) {
			jquery.get('README.md', function(data) {
				var converter = new Showdown.converter();
				jquery('#about .modal-body').html(converter.makeHtml(data));
			});
		});

		// Focus on message field
		jquery('#control_form .msg_field').focus();

		// Update form message size
		var form = jquery('#control_form');
		var field = form.find('.msg_field');
		var field_help = form.find('.msg_field_help');
		var field_max = parseInt(field.attr("maxlength"), 10);
		field.removeAttr("maxlength");
		function field_update() {
			var len = field.val().length;
			if(len < field_max) {
				// Success
				field_help.show();
				if(!form.hasClass('success')) form.toggleClass('success');
				if(form.hasClass('error')) form.toggleClass('error');
				field_help.text('' + (field_max-len));
			} else {
				// Error
				field_help.show();
				if(form.hasClass('success')) form.toggleClass('success');
				if(!form.hasClass('error')) form.toggleClass('error');
				field_help.text('' + (field_max-len));
			}
		}
		field.keydown(field_update);
		field.keyup(field_update);
		field.change(field_update);
	});
};

/* EOF */
<commit_msg>Updated<commit_after>/* Main code */

/* RequireJS configurations */
requirejs.config({
	shim: {
		'bootstrap':{
			deps: ['jquery']
		},
		'showdown':{
			deps: [],
			exports: 'Showdown'
		}
	}
});

var INFODESK_GLOBAL = {};

/* Pop error message */
function add_error(args, jquery) {
	//alert('error: '+ JSON.stringify(args));
	var data, dialog;
	if(args && (typeof args === 'object')) {
		if(args.error) {
			args.title = args.error;
			delete args.error;
		}
		if(args.title || args.desc) {
			data = args;
		} else {
			data = {'desc': JSON.stringify(args)};
		}
	} else {
		data = {'title':''+args};
	}
	if(!data.date) data.date = new Date();
	function do_jquery(jquery) {
		dialog = jquery('#elements .error_dialog').clone();
		dialog.find('.date').text(data.date);
		dialog.find('.title').text(data.title);
		if(data.desc && ((data.desc+'').length > 0) ) {
			dialog.find('.desc').text(data.desc);
		} else {
			dialog.find('.desc_container').hide();
		}
		dialog.prependTo('#notifications');
		dialog.alert();
	}
	if(jquery) {
		do_jquery(jquery);
	} else {
		require(["jquery"], function(jquery) {
			do_jquery(jquery);
		}, function(err) {
			alert('Error: ' . err);
		});
	}
};

/* Post message to server */
function remove_msg(log_id, success_fn) {
		var jqxhr;
		require(["jquery"], function(jquery) {
			jqxhr = jquery.post('backend.php', {'remove_msg':log_id});
		}, function(err) { add_error(JSON.stringify(err)); });
		jqxhr.complete(function(response) {
			try {
				if(response && response.status && (200 === response.status) && (response.responseText.substr(0, 2) === 'OK') ) {
					success_fn();
				} else if(response && (response.status !== undefined)) {
					add_error({'title':'Connection failed with #' + response.status, 'desc':response.responseText});
				} else {
					add_error('Connection failed');
				}
			} catch(e) {
				add_error('Connection failed');
			}
		});
}

/* Post message to server */
function post_msg(args) {
		//alert('In post_msg() with args=' + JSON.stringify(args) );
		var args = args || {};
		var msg = (args && (typeof args === 'object') && args.msg) ? ''+args.msg : '';
		if( (msg.length === 0) || (msg.length >= 1024) ) {
			return;
		}
		var jqxhr;
		require(["jquery"], function(jquery) {
			jqxhr = jquery.post('backend.php', {'send_msg':'1', 'msg':''+msg});
		}, function(err) { add_error(JSON.stringify(err)); });
		jqxhr.complete(function(response) {
			try {
				if(response && response.status && (200 === response.status) && (response.responseText.substr(0, 2) === 'OK') ) {
					require(["jquery"], function(jquery) {
						jquery("#control_form .msg_field").val('');
					}, function(err) { add_error(JSON.stringify(err)); });
					update_events();
				} else if(response && (response.status !== undefined)) {
					add_error({'title':'Connection failed with #' + response.status, 'desc':response.responseText});
				} else {
					add_error('Connection failed');
				}
			} catch(e) {
				add_error('Connection failed');
			}
		});
	/*
	require(["jquery"], function(jquery) {
	}, function(err) {
		add_error({'title':'Connection failed', 'desc':JSON.stringify(err)});
	});
	*/
}

/* Post message to server */
function post_msg_form() {
	var msg;
	require(["jquery"], function(jquery) {
		msg = jquery('#control_form').find('.msg_field').val();
	}, function(err) {
		add_error({'title':'Clearing form failed', 'desc':JSON.stringify(err)});
	});
	//alert('Calling post_msg() with msg=' + msg);
	post_msg({'msg':msg});
	return false;
}

/* */
function change_search_string(q) {

	if(INFODESK_GLOBAL.updating === true) { return; }

	//alert(hashtag);
	INFODESK_GLOBAL.search_string = ''+q;
	require(["jquery"], function(jquery) {
		jquery('#events .events-header').empty();
		jquery('#events .events-body').empty();
		if(q !== '') {
			var header = jquery('
').html( 'Results for '+format_msg(jquery, ''+q) + ' ' );
			var link = jquery('').html("Close ×").click(function(){ change_search_string(''); });
			link.appendTo(header);
			header.appendTo('#events .events-header');
		}
		jquery('#search_field').val('');
	}, function(err) { add_error(JSON.stringify(err)); });
	INFODESK_GLOBAL.last_id = 0;
	update_events();
}

/* */
function format_msg(jquery, msg) {
	msg = jquery('
').text(msg).html();

	// Format hashtags
	msg = msg.replace(/#([a-zA-Z0-9\.]+)/g, function($0, $1) {
		var h = (''+$1).toLowerCase();
		var div = jquery('
');
		var a = jquery('').text('#'+$1);
		a.appendTo(div);
		return div.html();
	});

	return msg;
}

/* */
function update_events() {
	var next_id, hashtag;
	if(INFODESK_GLOBAL.updating === true) { return; }
	INFODESK_GLOBAL.updating = true;
	next_id = INFODESK_GLOBAL.last_id+1;
	require(["jquery", "moment"], function(jquery, moment) {
		var options = {'msgs':'1', 'start':''+next_id};
		if(INFODESK_GLOBAL.search_string && (INFODESK_GLOBAL.search_string !== '')) {
			options.q = INFODESK_GLOBAL.search_string;
		}
		var jqxhr = jquery.get('backend.php', options, function(data) {
			var response = JSON.parse(data),
			    events = response.events,
			    server_time = response.time,
			    our_user_id = response.user_id;

			//alert('got events: ' + events.length);
			for(i in events) if(events.hasOwnProperty(i)) {
				(function() {
					var event = events[i],
					    id = parseInt(event.log_id, 10),
					    seconds_since = server_time - event.updated,
					    updated = new Date();

					updated.setTime( event.updated*1000 );

					if(id > INFODESK_GLOBAL.last_id) {
						INFODESK_GLOBAL.last_id = id;
					}

					var div = jquery('#elements .event_container').clone();
					div.find('.log_id').text(''+event.log_id);
					div.find('.date').text( moment(updated).format("HH:mm:ss") );
					div.find('.msg').html( format_msg(jquery, event.msg) );

					var close = div.find('.close');
					if ( (our_user_id === event.user_id) && (seconds_since < 5*60) ) {
						//alert("seconds_since = " + seconds_since);
						close.show();
						setTimeout(function() {
							close.hide();
						}, (5*60 - seconds_since)*1000);
					} else {
						close.hide();
					}

					close.click(function() {
						remove_msg(event.log_id, function() {
							div.remove();
						});
						return false;
					});

					div.prependTo('#events .events-body');
				}());
			}
			INFODESK_GLOBAL.updating = false;
		});
		jqxhr.complete(function(data, status) {
			if(status === "success") return;
			if(data && data.responseText) {
				try {
					var obj = JSON.parse(data.responseText);
					if(obj && obj.error) {
						add_error(obj.error, jquery);
						return;
					}
					add_error(obj);
				} catch(e) {
					add_error(data);
				}
			} else {
				add_error(status + ' with ' + data);
			}
		});
	}, function(err) { add_error(JSON.stringify(err)); });
}

/* */
function update_clock() {
	function f(d) { return ((''+d).length===1) ? '0'+d : ''+d; }
	require(["jquery"], function(jquery) {
		var now = new Date();
		jquery('#clock').val( f(now.getHours()) + ':' + f(now.getMinutes()) );
	});
}

/* */
INFODESK_GLOBAL.timer = undefined;
INFODESK_GLOBAL.updating = false;
INFODESK_GLOBAL.last_id = 0;
function update_events_timer() {
	update_events();
	//update_clock();
	setTimeout('update_events_timer()', 1000);
}

/* Init everything at onLoad event */
window.onload = function() {
	require(["bootstrap"], function(b) {});

	// TODO: Setup simple clock on control form

	// TODO: Setup previous event history
	// TODO: Start fetching new events
	//update_events();
	update_events_timer();

	//
	require(['jquery'], function(jquery) {

		// Setup ajax calls
		jquery.ajaxSetup({cache:false});

		// Setup search form
		jquery('.form-search').submit(function() {
			var q = jquery('#search_field').val();
			change_search_string(q);
			return false;
		});

		// Setup about modal's body
		require(['showdown'], function(Showdown) {
			jquery.get('README.md', function(data) {
				var converter = new Showdown.converter();
				jquery('#about .modal-body').html(converter.makeHtml(data));
			});
		});

		// Focus on message field
		jquery('#control_form .msg_field').focus();

		// Update form message size
		var form = jquery('#control_form');
		var field = form.find('.msg_field');
		var field_help = form.find('.msg_field_help');
		var field_max = parseInt(field.attr("maxlength"), 10);
		field.removeAttr("maxlength");
		function field_update() {
			var len = field.val().length;
			if(len < field_max) {
				// Success
				field_help.show();
				if(!form.hasClass('success')) form.toggleClass('success');
				if(form.hasClass('error')) form.toggleClass('error');
				field_help.text('' + (field_max-len));
			} else {
				// Error
				field_help.show();
				if(form.hasClass('success')) form.toggleClass('success');
				if(!form.hasClass('error')) form.toggleClass('error');
				field_help.text('' + (field_max-len));
			}
		}
		field.keydown(field_update);
		field.keyup(field_update);
		field.change(field_update);
	});
};

/* EOF */
<|endoftext|>


<commit_before>/*! @license ©2014 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University */
/* Writer that serializes a SPARQL query result application/sparql+xml */

var SparqlResultWriter = require('./SparqlResultWriter'),
    _ = require('lodash'),
    util = require('../util/RdfUtil'),
    xml = require('xml');

function SparqlXMLResultWriter(sparqlIterator) {
  SparqlResultWriter.call(this, true, sparqlIterator);
}
SparqlResultWriter.inherits(SparqlXMLResultWriter);

SparqlXMLResultWriter.prototype._writeHead = function (variableNames) {
  var root = this._root = xml.element({ _attr: { xlmns: 'http://www.w3.org/2005/sparql-results#' } }),
      results = this._results = xml.element({}), self = this;
  xml({ sparql: root }, { stream: true, indent: '  ', declaration: true })
     .on('data', function (chunk) { self._push(chunk + '\n'); });
  if (variableNames.length)
    root.push({ head: variableNames.map(function (v) { return { variable: { _attr: { name: v } } }; }) });
  root.push({ results: results });
};

SparqlXMLResultWriter.prototype._writeBindings = function (result) {
  //Unbounded variables cannot be part of xml
  result = _.omit(result, function (value) {
    console.log(value);
    return value === undefined || value === null;
  });
  this._results.push({
    result: _.map(result, function (value, variable) {
      var xmlValue, lang, type;
      if (!util.isLiteral(value))
        xmlValue = util.isBlank(value) ? { bnode: value } : { uri: value };
      else {
        xmlValue = { literal: util.getLiteralValue(value) };
        if (lang = util.getLiteralLanguage(value))
          xmlValue.literal = [{ _attr: { 'xml:lang': lang } }, xmlValue.literal];
        else if (type = util.getLiteralType(value))
          xmlValue.literal = [{ _attr: {   datatype: type } }, xmlValue.literal];
      }
      return { binding: [{ _attr: { name: variable.substring(1) } }, xmlValue] };
    })
  });
};

SparqlXMLResultWriter.prototype._writeBoolean = function (result) {
  this._root.push({ boolean: result });
};

SparqlXMLResultWriter.prototype._end = function () {
  this._results.close();
  this._root.close();
  SparqlResultWriter.prototype._end.call(this);
};

module.exports = SparqlXMLResultWriter;
<commit_msg>Removed debug log statement<commit_after>/*! @license ©2014 Miel Vander Sande - Multimedia Lab / iMinds / Ghent University */
/* Writer that serializes a SPARQL query result application/sparql+xml */

var SparqlResultWriter = require('./SparqlResultWriter'),
    _ = require('lodash'),
    util = require('../util/RdfUtil'),
    xml = require('xml');

function SparqlXMLResultWriter(sparqlIterator) {
  SparqlResultWriter.call(this, true, sparqlIterator);
}
SparqlResultWriter.inherits(SparqlXMLResultWriter);

SparqlXMLResultWriter.prototype._writeHead = function (variableNames) {
  var root = this._root = xml.element({ _attr: { xlmns: 'http://www.w3.org/2005/sparql-results#' } }),
      results = this._results = xml.element({}), self = this;
  xml({ sparql: root }, { stream: true, indent: '  ', declaration: true })
     .on('data', function (chunk) { self._push(chunk + '\n'); });
  if (variableNames.length)
    root.push({ head: variableNames.map(function (v) { return { variable: { _attr: { name: v } } }; }) });
  root.push({ results: results });
};

SparqlXMLResultWriter.prototype._writeBindings = function (result) {
  //Unbounded variables cannot be part of xml
  result = _.omit(result, function (value) {
    return value === undefined || value === null;
  });
  this._results.push({
    result: _.map(result, function (value, variable) {
      var xmlValue, lang, type;
      if (!util.isLiteral(value))
        xmlValue = util.isBlank(value) ? { bnode: value } : { uri: value };
      else {
        xmlValue = { literal: util.getLiteralValue(value) };
        if (lang = util.getLiteralLanguage(value))
          xmlValue.literal = [{ _attr: { 'xml:lang': lang } }, xmlValue.literal];
        else if (type = util.getLiteralType(value))
          xmlValue.literal = [{ _attr: {   datatype: type } }, xmlValue.literal];
      }
      return { binding: [{ _attr: { name: variable.substring(1) } }, xmlValue] };
    })
  });
};

SparqlXMLResultWriter.prototype._writeBoolean = function (result) {
  this._root.push({ boolean: result });
};

SparqlXMLResultWriter.prototype._end = function () {
  this._results.close();
  this._root.close();
  SparqlResultWriter.prototype._end.call(this);
};

module.exports = SparqlXMLResultWriter;
<|endoftext|>


<commit_before>module('lively.ide.tools.TextEditor').requires('lively.persistence.BuildSpec').toRun(function() {

// currently used for filesystem read/write
module("lively.ide.CommandLineInterface").load();

var defaultExtent = lively.Config.defaultTextEditorExtent,
    extent = pt(defaultExtent[0], defaultExtent[1]) || pt(500, 400);

lively.BuildSpec('lively.ide.tools.TextEditor', {
    _Extent: extent,
    className: "lively.morphic.Window",
    contentOffset: lively.pt(4.0,22.0),
    state: "expanded",
    draggingEnabled: true,
    layout: {adjustForNewBounds: true},
    isTextEditor: true,
    submorphs: [{
        _BorderWidth: 1,
        _Position: lively.pt(4.0,22.0),
        className: "lively.morphic.Box",
        name: 'container',
        layout: {
            adjustForNewBounds: true,
            resizeHeight: true,
            resizeWidth: true
        },
        onWindowGetsFocus: function onWindowGetsFocus() {
            if (!this.lastFocused) this.lastFocused = this.get('editor');
            this.lastFocused.focus();
        },
        submorphs: [
        lively.BuildSpec('lively.ide.tools.CommandLine').customize({
            _Extent: extent.withY(18),
            clearOnInput: false,
            name: 'urlText',
            layout: {resizeWidth: true},
            focus: function focus() {
                var win = this.getWindow();
                win && win.targetMorph && (win.targetMorph.lastFocused = this);
                return $super();
            }
        }), {
            className: "lively.morphic.Button",
            name: 'saveButton',
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(0.0,30.0),
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "save",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(300.0,30.0),
            className: "lively.morphic.Button",
            name: 'loadButton',
            doNotCopyProperties: [],
            doNotSerialize: [],
            droppingEnabled: false,
            grabbingEnabled: false,
            isPressed: false,
            label: "load",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(600.0,30.0),
            className: "lively.morphic.Button",
            name: 'removeButton',
            // droppingEnabled: false,
            // grabbingEnabled: false,
            label: "remove",
            layout: {
                scaleHorizontal: true
            }
        },{
            _FontFamily: "Monaco,monospace",
            className: "lively.morphic.CodeEditor",
            name: 'editor',
            evalEnabled: false,
            grabbingEnabled: false,
            theme: Config.get("aceTextEditorTheme"),
            layout: {resizeHeight: true,resizeWidth: true},
            sourceModule: "lively.ide.CodeEditor",
            textString: "",
            sourceNameForEval: function sourceNameForEval() {
                return this.getWindow().getLocation(true/*asstring*/);
            },
            focus: function focus() {
                var win = this.getWindow();
                win && win.targetMorph && (win.targetMorph.lastFocused = this);
                return $super();
            }
        }]
    }],
    titleBar: "TextEditor",
    onFromBuildSpecCreated: function onFromBuildSpecCreated() {
        $super();
        var win = this,
            editor = win.get('editor'),
            loadButton = win.get('loadButton'),
            saveButton = win.get('saveButton'),
            removeButton = win.get('removeButton'),
            container = win.get('container'),
            urlText = this.get('urlText');
        container.setBounds(win.innerBounds().insetByRect(lively.rect(win.contentOffset, pt(4,4))));
        urlText.setBounds(container.getExtent().withY(18).extentAsRectangle());
        var third = container.getExtent().x/3, pos = pt(0,18);
        loadButton.setBounds(pos.extent(pt(third, 22)));
        saveButton.setBounds(pos.withX(third).extent(pt(third, 22)));
        removeButton.setBounds(pos.withX(2*third).extent(pt(third, 22)));
        editor.setBounds(lively.rect(loadButton.bounds().bottomLeft(), container.innerBounds().bottomRight()));
    },
    connectionRebuilder: function connectionRebuilder() {
        var urlText = this.get('urlText'),
            editor = this.get('editor'),
            loadButton = this.get('loadButton'),
            saveButton = this.get('saveButton'),
            removeButton = this.get('removeButton'),
            container = this.get('container');
        lively.bindings.connect(urlText, 'savedTextString', this, 'loadFile');
        lively.bindings.connect(loadButton, 'fire', this, 'loadFile');
        lively.bindings.connect(removeButton, 'fire', this, 'removeFile');
        lively.bindings.connect(saveButton, 'fire', this, 'saveFile');
        lively.bindings.connect(editor, 'savedTextString', this, 'saveFile');
        lively.bindings.connect(this, 'contentStored', this, 'updateWindowTitle');
        lively.bindings.connect(this, 'contentLoaded', editor, 'textString');
        lively.bindings.connect(this, 'contentLoaded', this, 'gotoLocationLine');
        lively.bindings.connect(this, 'contentLoaded', this, 'updateWindowTitle');
        lively.bindings.connect(this, 'contentLoaded', editor, 'setTabSize', {updater: function($upd) {
            this.sourceObj.get('editor').guessAndSetTabSize();
        }});
        lively.bindings.connect(this, 'contentLoaded', editor, 'setTextMode', {updater: function($upd) {
            var ext = this.sourceObj.getFileExtension().toLowerCase();
            switch(ext) {
                case "r": $upd("r"); return;
                case "css": $upd("css"); return;
                case "h": case "c": case "cpp": $upd("c_cpp"); return;
                case "diff": $upd("diff"); return;
                case "xhtml": case "html": $upd("html"); return;
                case "js": $upd("javascript"); return;
                case "json": $upd("json"); return;
                case "jade": $upd("jade"); return;
                case "ejs": $upd("ejs"); return;
                case "markdown": case "md": $upd("markdown"); return;
                case "sh": $upd("sh"); return;
                case "xml": $upd("xml"); return;
                case "svg": $upd("svg"); return;
                case "lisp": case "el": $upd("lisp"); return;
                case "clj": case "cljs": case "cljx": $upd("clojure"); return;
                case "cabal": case "hs": $upd("haskell"); return;
                case "py": $upd("python"); return;
                default: $upd("text");
            }
        }});
    },
    getLine: function getLine() {
        var string = this.get('urlText').textString,
            match = string.match(/:([0-9]+)$/);
        return match && Number(match[1]);
    },
    gotoLocationLine: function gotoLocationLine() {
        var line = this.getLine();
        if (!line) return;
        var editor = this.get('editor');
        editor.scrollToRow(line);
        editor.setCursorPosition(pt(0, line-1));
    },
    getLocation: function getLocation(asString) {
        var string = this.get('urlText').textString;
        string = string.replace(/:[0-9]+$/, '');
        if (asString) return string;
        try {
            return new URL(string);
        } catch(e) {
            return string;
        }
    },
    getFileExtension: function getFileExtension() {
        return this.getLocation(true).split('.').last();
    },
    getWebResource: function getWebResource() {
        var loc = this.getLocation();
        return loc.isURL && loc.asWebResource();
    },
    loadFile: function loadFile() {
        if (this.getLocation().isURL) {
            this.loadFileNetwork();
        } else {
            this.loadFileFileSystem();
        }
    },
    loadFileFileSystem: function loadFileFileSystem() {
        var path = this.getLocation(true),
            cwd = lively.shell.cwd();
        // if (path.indexOf(cwd) === 0) {
        //     path = path.slice(cwd.length);
        //     if (path[0] === '/' || path[0] === '\\') path = path.slice(1);
        //     var urlText = this.get('urlText');
        //     lively.bindings.noUpdate(function() {
        //         urlText.textString = path;
        //     });
        // }
        lively.ide.CommandLineInterface.readFile(path, {}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            if (err) { this.message(Strings.format("Could not read file.\nError: %s", err)); return; }
            lively.bindings.signal(this, 'contentLoaded', cmd.getStdout());
        }.bind(this));
    },
    loadFileNetwork: function loadFileNetwork() {
        var webR = this.getWebResource();
        connect(webR, 'content', this, 'contentLoaded');
        webR.beAsync().forceUncached().get();
    },

    saveFile: function saveFile(thenDo) {
        var loc = this.getLocation(),
            selector = loc.isURL ? "saveFileNetwork" : "saveFileFileSystem",
            self = this;
        Functions.composeAsync(
            function(next) {
                self[selector](function(err) {
                    if (!err) {
                        self.message(String(loc) + ' saved', Color.green);
                        lively.bindings.signal(self, 'contentStored');
                        next();
                    } else {
                        self.message(Strings.format("Could not save.\nError: %s", err), Color.red);
                        next(err);
                    }
                });
            },
            function(next) {
                lively.lang.Runtime.resourceChanged(
                  String(loc), self.get('editor').textString, next);
            }
        )();
    },

    saveFileFileSystem: function saveFileFileSystem(thenDo) {
        var path = lively.shell.makeAbsolute(this.getLocation(true)),
            content = this.get('editor').textString;
        lively.ide.CommandLineInterface.writeFile(path, {content: content}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            thenDo && thenDo(err ? new Error(err) : null);
        }.bind(this));
    },

    saveFileNetwork: function saveFileNetwork(thenDo) {
        var ed = this, webR = this.getWebResource();
        webR.beAsync().noProxy().put(this.get('editor').textString)
            .whenDone(function(_, status) {
                thenDo && thenDo(status.isSuccess() ?
                    null : new Error(String(status))); });
    },

    updateWindowTitle: function updateWindowTitle() {
        var location = this.getLocation();
        this.setTitle(String(location));
    },
    removeFile: function removeFile() {
        var webR = this.getWebResource();
        webR.statusMessage(webR.getURL() + ' removed', webR.getURL() + ' could not removed!');
        webR.beAsync().del();
    },
    openURL: function openURL(url) {
        this.get('urlText').textString = String(url);
        this.loadFile();
    },
    message: function message(/*msg, color, ...*/) { var ed = this.get('editor'); ed.setStatusMessage.apply(ed,arguments); },
    onKeyDown: function onKeyDown(evt) {
        if (this.showsHalos) return $super(evt);
        var sig = evt.getKeyString();
        switch(sig) {
            case 'Alt-Up': case 'F1': this.get('urlText').focus(); evt.stop(); return true;
            case 'Alt-Down': case 'F2': this.get('editor').focus(); evt.stop(); return true;
            case "Command-U":
                $world.confirm('Revert input / reload file?', function(input) {
                    if (!input) { alertOK('Revert canceled'); return; }
                    this.loadFile();
                }.bind(this));
                evt.stop(); return true;
            default: return $super(evt);
        }
    }
});

}) // end of module<commit_msg>TextEditor: fix remoaval of file system resources<commit_after>module('lively.ide.tools.TextEditor').requires('lively.persistence.BuildSpec').toRun(function() {

// currently used for filesystem read/write
module("lively.ide.CommandLineInterface").load();

var defaultExtent = lively.Config.defaultTextEditorExtent,
    extent = pt(defaultExtent[0], defaultExtent[1]) || pt(500, 400);

lively.BuildSpec('lively.ide.tools.TextEditor', {
    _Extent: extent,
    className: "lively.morphic.Window",
    contentOffset: lively.pt(4.0,22.0),
    state: "expanded",
    draggingEnabled: true,
    layout: {adjustForNewBounds: true},
    isTextEditor: true,
    submorphs: [{
        _BorderWidth: 1,
        _Position: lively.pt(4.0,22.0),
        className: "lively.morphic.Box",
        name: 'container',
        layout: {
            adjustForNewBounds: true,
            resizeHeight: true,
            resizeWidth: true
        },
        onWindowGetsFocus: function onWindowGetsFocus() {
            if (!this.lastFocused) this.lastFocused = this.get('editor');
            this.lastFocused.focus();
        },
        submorphs: [
        lively.BuildSpec('lively.ide.tools.CommandLine').customize({
            _Extent: extent.withY(18),
            clearOnInput: false,
            name: 'urlText',
            layout: {resizeWidth: true},
            focus: function focus() {
                var win = this.getWindow();
                win && win.targetMorph && (win.targetMorph.lastFocused = this);
                return $super();
            }
        }), {
            className: "lively.morphic.Button",
            name: 'saveButton',
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(0.0,30.0),
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "save",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(300.0,30.0),
            className: "lively.morphic.Button",
            name: 'loadButton',
            doNotCopyProperties: [],
            doNotSerialize: [],
            droppingEnabled: false,
            grabbingEnabled: false,
            isPressed: false,
            label: "load",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(600.0,30.0),
            className: "lively.morphic.Button",
            name: 'removeButton',
            // droppingEnabled: false,
            // grabbingEnabled: false,
            label: "remove",
            layout: {
                scaleHorizontal: true
            }
        },{
            _FontFamily: "Monaco,monospace",
            className: "lively.morphic.CodeEditor",
            name: 'editor',
            evalEnabled: false,
            grabbingEnabled: false,
            theme: Config.get("aceTextEditorTheme"),
            layout: {resizeHeight: true,resizeWidth: true},
            sourceModule: "lively.ide.CodeEditor",
            textString: "",
            sourceNameForEval: function sourceNameForEval() {
                return this.getWindow().getLocation(true/*asstring*/);
            },
            focus: function focus() {
                var win = this.getWindow();
                win && win.targetMorph && (win.targetMorph.lastFocused = this);
                return $super();
            }
        }]
    }],
    titleBar: "TextEditor",
    onFromBuildSpecCreated: function onFromBuildSpecCreated() {
        $super();
        var win = this,
            editor = win.get('editor'),
            loadButton = win.get('loadButton'),
            saveButton = win.get('saveButton'),
            removeButton = win.get('removeButton'),
            container = win.get('container'),
            urlText = this.get('urlText');
        container.setBounds(win.innerBounds().insetByRect(lively.rect(win.contentOffset, pt(4,4))));
        urlText.setBounds(container.getExtent().withY(18).extentAsRectangle());
        var third = container.getExtent().x/3, pos = pt(0,18);
        loadButton.setBounds(pos.extent(pt(third, 22)));
        saveButton.setBounds(pos.withX(third).extent(pt(third, 22)));
        removeButton.setBounds(pos.withX(2*third).extent(pt(third, 22)));
        editor.setBounds(lively.rect(loadButton.bounds().bottomLeft(), container.innerBounds().bottomRight()));
    },
    connectionRebuilder: function connectionRebuilder() {
        var urlText = this.get('urlText'),
            editor = this.get('editor'),
            loadButton = this.get('loadButton'),
            saveButton = this.get('saveButton'),
            removeButton = this.get('removeButton'),
            container = this.get('container');
        lively.bindings.connect(urlText, 'savedTextString', this, 'loadFile');
        lively.bindings.connect(loadButton, 'fire', this, 'loadFile');
        lively.bindings.connect(removeButton, 'fire', this, 'removeFile');
        lively.bindings.connect(saveButton, 'fire', this, 'saveFile');
        lively.bindings.connect(editor, 'savedTextString', this, 'saveFile');
        lively.bindings.connect(this, 'contentStored', this, 'updateWindowTitle');
        lively.bindings.connect(this, 'contentLoaded', editor, 'textString');
        lively.bindings.connect(this, 'contentLoaded', this, 'gotoLocationLine');
        lively.bindings.connect(this, 'contentLoaded', this, 'updateWindowTitle');
        lively.bindings.connect(this, 'contentLoaded', editor, 'setTabSize', {updater: function($upd) {
            this.sourceObj.get('editor').guessAndSetTabSize();
        }});
        lively.bindings.connect(this, 'contentLoaded', editor, 'setTextMode', {updater: function($upd) {
            var ext = this.sourceObj.getFileExtension().toLowerCase();
            switch(ext) {
                case "r": $upd("r"); return;
                case "css": $upd("css"); return;
                case "h": case "c": case "cpp": $upd("c_cpp"); return;
                case "diff": $upd("diff"); return;
                case "xhtml": case "html": $upd("html"); return;
                case "js": $upd("javascript"); return;
                case "json": $upd("json"); return;
                case "jade": $upd("jade"); return;
                case "ejs": $upd("ejs"); return;
                case "markdown": case "md": $upd("markdown"); return;
                case "sh": $upd("sh"); return;
                case "xml": $upd("xml"); return;
                case "svg": $upd("svg"); return;
                case "lisp": case "el": $upd("lisp"); return;
                case "clj": case "cljs": case "cljx": $upd("clojure"); return;
                case "cabal": case "hs": $upd("haskell"); return;
                case "py": $upd("python"); return;
                default: $upd("text");
            }
        }});
    },
    getLine: function getLine() {
        var string = this.get('urlText').textString,
            match = string.match(/:([0-9]+)$/);
        return match && Number(match[1]);
    },
    gotoLocationLine: function gotoLocationLine() {
        var line = this.getLine();
        if (!line) return;
        var editor = this.get('editor');
        editor.scrollToRow(line);
        editor.setCursorPosition(pt(0, line-1));
    },
    getLocation: function getLocation(asString) {
        var string = this.get('urlText').textString;
        string = string.replace(/:[0-9]+$/, '');
        if (asString) return string;
        try {
            return new URL(string);
        } catch(e) {
            return string;
        }
    },
    getFileExtension: function getFileExtension() {
        return this.getLocation(true).split('.').last();
    },
    getWebResource: function getWebResource() {
        var loc = this.getLocation();
        return loc.isURL && loc.asWebResource();
    },
    loadFile: function loadFile() {
        if (this.getLocation().isURL) {
            this.loadFileNetwork();
        } else {
            this.loadFileFileSystem();
        }
    },
    loadFileFileSystem: function loadFileFileSystem() {
        var path = this.getLocation(true),
            cwd = lively.shell.cwd();
        // if (path.indexOf(cwd) === 0) {
        //     path = path.slice(cwd.length);
        //     if (path[0] === '/' || path[0] === '\\') path = path.slice(1);
        //     var urlText = this.get('urlText');
        //     lively.bindings.noUpdate(function() {
        //         urlText.textString = path;
        //     });
        // }
        lively.ide.CommandLineInterface.readFile(path, {}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            if (err) { this.message(Strings.format("Could not read file.\nError: %s", err)); return; }
            lively.bindings.signal(this, 'contentLoaded', cmd.getStdout());
        }.bind(this));
    },
    loadFileNetwork: function loadFileNetwork() {
        var webR = this.getWebResource();
        connect(webR, 'content', this, 'contentLoaded');
        webR.beAsync().forceUncached().get();
    },

    saveFile: function saveFile(thenDo) {
        var loc = this.getLocation(),
            selector = loc.isURL ? "saveFileNetwork" : "saveFileFileSystem",
            self = this;
        Functions.composeAsync(
            function(next) {
                self[selector](function(err) {
                    if (!err) {
                        self.message(String(loc) + ' saved', Color.green);
                        lively.bindings.signal(self, 'contentStored');
                        next();
                    } else {
                        self.message(Strings.format("Could not save.\nError: %s", err), Color.red);
                        next(err);
                    }
                });
            },
            function(next) {
                lively.lang.Runtime.resourceChanged(
                  String(loc), self.get('editor').textString, next);
            }
        )();
    },

    saveFileFileSystem: function saveFileFileSystem(thenDo) {
        var path = lively.shell.makeAbsolute(this.getLocation(true)),
            content = this.get('editor').textString;
        lively.ide.CommandLineInterface.writeFile(path, {content: content}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            thenDo && thenDo(err ? new Error(err) : null);
        }.bind(this));
    },

    saveFileNetwork: function saveFileNetwork(thenDo) {
        var ed = this, webR = this.getWebResource();
        webR.beAsync().noProxy().put(this.get('editor').textString)
            .whenDone(function(_, status) {
                thenDo && thenDo(status.isSuccess() ?
                    null : new Error(String(status))); });
    },

    updateWindowTitle: function updateWindowTitle() {
        var location = this.getLocation();
        this.setTitle(String(location));
    },

    removeFile: function removeFile() {
      var loc = this.getLocation();
      if (loc.isURL) {
        var webR = this.getWebResource()
          .statusMessage(webR.getURL() + ' removed', webR.getURL() + ' could not removed!')
          .beAsync().del();
      } else {
        var ed = this.get('editor');
        lively.shell.rm(loc, function(err) {
          ed.setStatusMessage(
            err ? loc + "could not be deleted:\n" + (err.stack || err) : loc + " was deleted",
            err ? Color.red : Color.green,
            err ? 5 : undefined); });
      }
    },

    openURL: function openURL(url) {
        this.get('urlText').textString = String(url);
        this.loadFile();
    },
    message: function message(/*msg, color, ...*/) { var ed = this.get('editor'); ed.setStatusMessage.apply(ed,arguments); },
    onKeyDown: function onKeyDown(evt) {
        if (this.showsHalos) return $super(evt);
        var sig = evt.getKeyString();
        switch(sig) {
            case 'Alt-Up': case 'F1': this.get('urlText').focus(); evt.stop(); return true;
            case 'Alt-Down': case 'F2': this.get('editor').focus(); evt.stop(); return true;
            case "Command-U":
                $world.confirm('Revert input / reload file?', function(input) {
                    if (!input) { alertOK('Revert canceled'); return; }
                    this.loadFile();
                }.bind(this));
                evt.stop(); return true;
            default: return $super(evt);
        }
    }
});

}) // end of module<|endoftext|>


<commit_before>import { createSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import castArray from 'lodash/castArray';
import flatMap from 'lodash/flatMap';
import uniqBy from 'lodash/uniqBy';
import uniq from 'lodash/uniq';
import groupBy from 'lodash/groupBy';
import { toPlural } from 'utils/ghg-emissions';
import {
  getYColumnValue,
  getThemeConfig,
  getTooltipConfig,
  setXAxisDomain,
  setYAxisDomain,
  sortEmissionsByValue
} from 'utils/graphs';
import {
  DATA_SCALE,
  DEFAULT_AXES_CONFIG,
  GHG_TABLE_HEADER,
  NEW_CHART_COLORS_BASIC,
  NEW_CHART_COLORS,
  NEW_CHART_COLORS_EXTENDED,
  OTHER_COLOR
} from 'data/constants';
import { getWBData, getData, getRegions } from './ghg-emissions-selectors-get';
import {
  getModelSelected,
  getMetricSelected,
  getOptionsSelected,
  getOptions
} from './ghg-emissions-selectors-filters';

const LEGEND_LIMIT = 10;

const getShouldExpand = filter =>
  createSelector([getModelSelected, getOptionsSelected], (modelSelected, selectedOptions) => {
    const model = modelSelected && toPlural(modelSelected);
    const dataSelected = selectedOptions[`${filter}Selected`];

    if (!selectedOptions || !model || model !== filter || !dataSelected) {
      return false;
    }

    return !!(
      dataSelected.length === 1 &&
      dataSelected[0].expandsTo &&
      dataSelected[0].expandsTo.length
    );
  });

const getShouldExpandRegions = getShouldExpand('regions');
const getShouldExpandGases = getShouldExpand('gases');
const getShouldExpandSectors = getShouldExpand('sectors');

const getExpandedLegendRegionsSelected = createSelector(
  [getOptions, getOptionsSelected, getShouldExpandRegions, getData],
  (options, selectedOptions, shouldExpandRegions, data) => {
    if (!shouldExpandRegions || !data) return null;

    const dataSelected = selectedOptions.regionsSelected;
    const expandedOption = dataSelected[0];
    const countryOptions = expandedOption.expandsTo.map(iso =>
      options.regions.find(o => o.iso === iso)
    );
    const groupedCountries = data && groupBy(data, 'iso_code3');
    const getLastValue = x => x.emissions[x.emissions.length - 1].value;

    const latestValuesHash = {};
    Object.keys(groupedCountries).forEach(c => {
      latestValuesHash[c] = groupedCountries[c].reduce((acc, v) => acc + getLastValue(v), 0);
    });
    const byLatestYearEmission = (a, b) =>
      (latestValuesHash[a.iso] > latestValuesHash[b.iso] ? -1 : 1);
    const sortedCountries = countryOptions.sort(byLatestYearEmission);

    if (data && sortedCountries.length > LEGEND_LIMIT) {
      const othersGroup = data && sortedCountries.slice(LEGEND_LIMIT, -1).filter(o => o);
      const othersOption = {
        iso: 'OTHERS',
        label: 'Others',
        value: 'OTHERS',
        expandsTo: othersGroup.map(o => o.iso),
        expandedOptionValue: expandedOption.iso,
        groupId: 'regions'
      };
      return data && [...sortedCountries.slice(0, LEGEND_LIMIT), othersOption];
    }

    return sortedCountries;
  }
);

const getExpandedLegendSectorsSelected = createSelector(
  [getModelSelected, getOptions, getOptionsSelected, getShouldExpandSectors],
  (modelSelected, options, selectedOptions, shouldExpandSectors) => {
    const model = toPlural(modelSelected);
    if (!shouldExpandSectors) return null;

    const selectedSector = selectedOptions.sectorsSelected[0];
    const sectorOptions = selectedSector.expandsTo.map(id =>
      options[model].find(o => o.value === id)
    );

    return sectorOptions;
  }
);

const getExpandedLegendGasesSelected = createSelector(
  [getModelSelected, getOptions, getOptionsSelected, getShouldExpandGases],
  (modelSelected, options, selectedOptions, shouldExpandGases) => {
    const model = toPlural(modelSelected);
    if (!shouldExpandGases) return null;

    const selectedGas = selectedOptions.gasesSelected[0];
    const gasOptions = selectedGas.expandsTo.map(id => options[model].find(o => o.value === id));

    return gasOptions;
  }
);

export const getLegendDataOptions = createSelector(
  [getModelSelected, getOptions],
  (modelSelected, options) => {
    if (!options || !modelSelected || !options[toPlural(modelSelected)]) {
      return null;
    }
    return options[toPlural(modelSelected)];
  }
);

export const getLegendDataSelected = createSelector(
  [
    getModelSelected,
    getOptions,
    getOptionsSelected,
    getExpandedLegendRegionsSelected,
    getExpandedLegendSectorsSelected,
    getExpandedLegendGasesSelected
  ],
  (
    modelSelected,
    options,
    selectedOptions,
    expandedLegendRegionsSelected,
    expandedLegendSectorsSelected,
    expandedLegendGasesSelected
  ) => {
    const model = toPlural(modelSelected);
    const selectedModel = `${model}Selected`;
    if (!selectedOptions || !modelSelected || !selectedOptions[selectedModel] || !options) {
      return null;
    }

    let dataSelected = selectedOptions[selectedModel];

    if (expandedLegendRegionsSelected) {
      dataSelected = expandedLegendRegionsSelected;
    } else if (expandedLegendSectorsSelected) {
      dataSelected = expandedLegendSectorsSelected;
    } else if (expandedLegendGasesSelected) {
      dataSelected = expandedLegendGasesSelected;
    }

    return castArray(dataSelected).filter(c => c && !c.hideLegend);
  }
);

const getExpandedData = createSelector(
  [getData, getLegendDataSelected],
  (data, legendDataSelected) => {
    if (!legendDataSelected || !data || !data.length) return null;
    const othersOption = legendDataSelected.find(o => o.iso === 'OTHERS');
    if (!othersOption) return data;

    const expandedRegionISO = othersOption.expandedOptionValue;
    const expandedRegionData = data.find(d => d.iso_code3 === expandedRegionISO);

    const othEmByYear = {};
    const regionBelongsToOthers = d => othersOption.expandsTo.includes(d.iso_code3);

    // if expanded region like for example WORLD has it's own data line
    // then use that Total value to calculate Others value
    if (expandedRegionData) {
      const expandedCountriesISOCodes = legendDataSelected
        .filter(o => o.iso !== 'OTHERS')
        .map(o => o.iso);
      const exCountriesEmByYear = {};
      data.forEach(d => {
        if (expandedCountriesISOCodes.includes(d.iso_code3)) {
          d.emissions.forEach(e => {
            exCountriesEmByYear[e.year] = (exCountriesEmByYear[e.year] || 0) + e.value;
          });
        }
      });

      expandedRegionData.emissions.forEach(e => {
        othEmByYear[e.year] = e.value - exCountriesEmByYear[e.year];
      });
    } else {
      data.forEach(d => {
        if (regionBelongsToOthers(d)) {
          d.emissions.forEach(e => {
            othEmByYear[e.year] = (othEmByYear[e.year] || 0) + e.value;
          });
        }
      });
    }

    const othersData = {
      iso_code3: 'OTHERS',
      location: 'Others',
      emissions: Object.keys(othEmByYear).map(year => ({
        year: Number(year),
        value: othEmByYear[year]
      }))
    };

    return [...data.filter(d => !regionBelongsToOthers(d)), othersData];
  }
);

const getSortedData = createSelector(getExpandedData, data => {
  if (!data || isEmpty(data)) return null;
  return sortEmissionsByValue(data);
});

const getYColumnOptions = createSelector([getLegendDataSelected], legendDataSelected => {
  if (!legendDataSelected) return null;
  const getYOption = columns =>
    columns &&
    columns.map(d => ({
      ...d,
      value: d && getYColumnValue(d.label)
    }));
  return uniqBy(getYOption(legendDataSelected), 'value');
});

const getCalculationData = createSelector([getWBData], data => {
  if (!data || isEmpty(data)) return null;
  const yearData = {};
  Object.keys(data).forEach(iso => {
    data[iso].forEach(d => {
      if (!yearData[d.year]) yearData[d.year] = {};
      yearData[d.year][iso] = { population: d.population, gdp: d.gdp };
    });
  });
  return yearData;
});

const calculateValue = (currentValue, value, metricRatio) => {
  const updatedValue = value || value === 0 ? value * (DATA_SCALE / metricRatio) : null;
  if (updatedValue && (currentValue || currentValue === 0)) {
    return updatedValue + currentValue;
  }
  return updatedValue || currentValue;
};

// some regions expands to underlying countries but also have
// their own aggregated data
const getRegionsWithOwnData = (regions, data) => {
  if (!regions || isEmpty(data)) return [];

  const regionsISOs = regions.map(r => r.iso_code3);
  const regionsISOsWithOwnData = uniq(
    data.filter(d => regionsISOs.includes(d.iso_code3)).map(d => d.iso_code3)
  );

  return regions.filter(r => regionsISOsWithOwnData.includes(r.iso_code3));
};

export const getChartData = createSelector(
  [
    getSortedData,
    getRegions,
    getModelSelected,
    getYColumnOptions,
    getMetricSelected,
    getCalculationData
  ],
  (data, regions, model, yColumnOptions, metric, calculationData) => {
    if (!data || !data.length || !model || !calculationData || !regions) return null;
    const yearValues = data[0].emissions.map(d => d.year);
    const metricField = {
      PER_CAPITA: 'population',
      PER_GDP: 'gdp'
    }[metric];
    const shouldHaveMetricData = !!metricField;

    const regionsWithOwnData = getRegionsWithOwnData(regions, data);
    const regionWithOwnDataMembers = uniq(
      flatMap(regionsWithOwnData, r => r.members.map(m => m.iso_code3))
    );
    const memberOfRegionWithOwnData = d => regionWithOwnDataMembers.includes(d.iso_code3);

    const isBreakByRegions = model === 'regions';
    const groupByKey = isBreakByRegions ? 'location' : model;
    const groupedData = groupBy(data, groupByKey);

    const expandedData = column => {
      if (!column.expandsTo) return null;

      return flatMap(column.expandsTo, e => data.filter(d => d.iso_code3 === e)).filter(d => d);
    };

    const expandRegionToCountries = iso => {
      const region = regions.find(r => r.iso_code3 === iso);
      if (!region || !region.members) return iso;
      return flatMap(region.members, expandRegionToCountries);
    };
    const expandRegionsToCountries = isos => uniq(flatMap(isos, expandRegionToCountries));

    const getMetricData = (year, region, column) => {
      const getMetricForYearAndRegion = (y, r) =>
        metricField &&
        calculationData &&
        calculationData[y] &&
        calculationData[y][r] &&
        calculationData[y][r][metricField];
      let metricData = getMetricForYearAndRegion(year, region);

      // if no metric data for expandable column then use expanded regions to
      // calculate metric data
      if (!metricData && column.expandsTo && column.expandsTo.length) {
        const expandedCountries = expandRegionsToCountries(column.expandsTo);
        metricData = expandedCountries.reduce(
          (acc, iso) => acc + (getMetricForYearAndRegion(year, iso) || 0),
          0
        );
      }

      // GDP is in dollars and we want to display it in million dollars
      if (metricField === 'gdp' && metricData) metricData /= 1000000;

      return metricData;
    };

    const dataParsed = yearValues.map(year => {
      const yItems = {};

      yColumnOptions.forEach(column => {
        const dataForColumn = groupedData[column.label] || expandedData(column) || [];

        dataForColumn.forEach(d => {
          if (!isBreakByRegions && memberOfRegionWithOwnData(d)) return;

          const yearEmissions = d.emissions.find(e => e.year === year);
          const key = column.value;
          const metricData = shouldHaveMetricData && getMetricData(year, d.iso_code3, column);
          const metricRatio = shouldHaveMetricData ? metricData : 1;

          if (yearEmissions && metricRatio) {
            yItems[key] = calculateValue(yItems[key], yearEmissions.value, metricRatio);
          }
        });
      });

      return {
        x: year,
        ...yItems
      };
    });

    // if there is no value for any legend item
    // remove those element from the start and the end of chart
    // leave those in the middle
    const trimWithNoData = dataToTrim => {
      const indexesToString = dataToTrim
        .map((d, idx) => (Object.keys(d).length > 1 ? idx : '_'))
        .join(',')
        .replace(/_,/g, '')
        .trim();
      const middleIndexes = indexesToString.split(',');
      const firstNotEmptyIndex = Number(middleIndexes[0]);
      const lastNotEmptyIndex = Number(middleIndexes[middleIndexes.length - 1]);

      return dataToTrim.filter((_d, idx) => idx >= firstNotEmptyIndex && idx <= lastNotEmptyIndex);
    };

    return trimWithNoData(dataParsed);
  }
);

export const getChartDomain = createSelector([getChartData], data => {
  if (!data) return null;
  return {
    x: setXAxisDomain(),
    y: setYAxisDomain()
  };
});

// variable that caches chart elements assigned color
// to avoid element color changing when the chart is updated
let colorThemeCache = {
  yOthers: { stroke: OTHER_COLOR, fill: OTHER_COLOR }
};

const getColorPalette = columns => {
  if (columns.length <= 10) return NEW_CHART_COLORS_BASIC;
  if (columns.length <= 20) return NEW_CHART_COLORS;
  return NEW_CHART_COLORS_EXTENDED;
};

const getUnit = metric => {
  let unit = DEFAULT_AXES_CONFIG.yLeft.unit;
  if (metric === 'PER_GDP') {
    unit = `${unit}/ million $ GDP`;
  } else if (metric === 'PER_CAPITA') {
    unit = `${unit} per capita`;
  }
  return unit;
};

export const getChartConfig = createSelector(
  [getModelSelected, getMetricSelected, getYColumnOptions],
  (model, metric, yColumns) => {
    if (!model || !yColumns) return null;
    const colorPalette = getColorPalette(yColumns);
    colorThemeCache = getThemeConfig(yColumns, colorPalette, colorThemeCache);
    const tooltip = getTooltipConfig(yColumns.filter(c => c && !c.hideLegend));
    const unit = getUnit(metric);
    return {
      axes: {
        ...DEFAULT_AXES_CONFIG,
        yLeft: {
          ...DEFAULT_AXES_CONFIG.yLeft,
          unit,
          suffix: 't'
        }
      },
      theme: colorThemeCache,
      tooltip,
      animation: false,
      columns: {
        x: [
          {
            label: 'year',
            value: 'x'
          }
        ],
        y: yColumns
      }
    };
  }
);

export const getTableData = createSelector(
  [getChartData, getMetricSelected, getModelSelected, getYColumnOptions],
  (data, metric, model, yColumnOptions) => {
    if (!data || !model || !data.length || !yColumnOptions) return null;

    const formatValue = value => value && Number(value.toFixed(2));
    const unit = getUnit(metric);

    const pivot = yColumnOptions.map(c => ({
      [GHG_TABLE_HEADER[model]]: c.label,
      unit,
      ...data.reduce(
        (acc, d) => ({
          ...acc,
          [String(d.x)]: formatValue(d[c.value]) // year: value
        }),
        {}
      )
    }));

    return pivot;
  }
);

export const getLoading = createSelector(
  [getChartConfig, state => state.ghgEmissionsMeta, state => state.emissions],
  (chartConfig, meta, data) =>
    (meta && meta.loading) || (data && data.loading) || !chartConfig || false
);

export const getHideRemoveOptions = createSelector(
  [getShouldExpandRegions, getShouldExpandSectors, getShouldExpandGases],
  (shouldExpandRegions, shouldExpandSectors, shouldExpandGases) =>
    shouldExpandRegions || shouldExpandSectors || shouldExpandGases
);
<commit_msg>ghg module: fix per gdp and per capita calculations for regions<commit_after>import { createSelector } from 'reselect';
import isEmpty from 'lodash/isEmpty';
import castArray from 'lodash/castArray';
import flatMap from 'lodash/flatMap';
import uniqBy from 'lodash/uniqBy';
import uniq from 'lodash/uniq';
import groupBy from 'lodash/groupBy';
import { toPlural } from 'utils/ghg-emissions';
import {
  getYColumnValue,
  getThemeConfig,
  getTooltipConfig,
  setXAxisDomain,
  setYAxisDomain,
  sortEmissionsByValue
} from 'utils/graphs';
import {
  DATA_SCALE,
  DEFAULT_AXES_CONFIG,
  GHG_TABLE_HEADER,
  NEW_CHART_COLORS_BASIC,
  NEW_CHART_COLORS,
  NEW_CHART_COLORS_EXTENDED,
  OTHER_COLOR
} from 'data/constants';
import { getWBData, getData, getRegions } from './ghg-emissions-selectors-get';
import {
  getModelSelected,
  getMetricSelected,
  getOptionsSelected,
  getOptions
} from './ghg-emissions-selectors-filters';

const LEGEND_LIMIT = 10;

const getShouldExpand = filter =>
  createSelector([getModelSelected, getOptionsSelected], (modelSelected, selectedOptions) => {
    const model = modelSelected && toPlural(modelSelected);
    const dataSelected = selectedOptions[`${filter}Selected`];

    if (!selectedOptions || !model || model !== filter || !dataSelected) {
      return false;
    }

    return !!(
      dataSelected.length === 1 &&
      dataSelected[0].expandsTo &&
      dataSelected[0].expandsTo.length
    );
  });

const getShouldExpandRegions = getShouldExpand('regions');
const getShouldExpandGases = getShouldExpand('gases');
const getShouldExpandSectors = getShouldExpand('sectors');

const getExpandedLegendRegionsSelected = createSelector(
  [getOptions, getOptionsSelected, getShouldExpandRegions, getData],
  (options, selectedOptions, shouldExpandRegions, data) => {
    if (!shouldExpandRegions || !data) return null;

    const dataSelected = selectedOptions.regionsSelected;
    const expandedOption = dataSelected[0];
    const countryOptions = expandedOption.expandsTo.map(iso =>
      options.regions.find(o => o.iso === iso)
    );
    const groupedCountries = data && groupBy(data, 'iso_code3');
    const getLastValue = x => x.emissions[x.emissions.length - 1].value;

    const latestValuesHash = {};
    Object.keys(groupedCountries).forEach(c => {
      latestValuesHash[c] = groupedCountries[c].reduce((acc, v) => acc + getLastValue(v), 0);
    });
    const byLatestYearEmission = (a, b) =>
      (latestValuesHash[a.iso] > latestValuesHash[b.iso] ? -1 : 1);
    const sortedCountries = countryOptions.sort(byLatestYearEmission);

    if (data && sortedCountries.length > LEGEND_LIMIT) {
      const othersGroup = data && sortedCountries.slice(LEGEND_LIMIT, -1).filter(o => o);
      const othersOption = {
        iso: 'OTHERS',
        label: 'Others',
        value: 'OTHERS',
        expandsTo: othersGroup.map(o => o.iso),
        expandedOptionValue: expandedOption.iso,
        groupId: 'regions'
      };
      return data && [...sortedCountries.slice(0, LEGEND_LIMIT), othersOption];
    }

    return sortedCountries;
  }
);

const getExpandedLegendSectorsSelected = createSelector(
  [getModelSelected, getOptions, getOptionsSelected, getShouldExpandSectors],
  (modelSelected, options, selectedOptions, shouldExpandSectors) => {
    const model = toPlural(modelSelected);
    if (!shouldExpandSectors) return null;

    const selectedSector = selectedOptions.sectorsSelected[0];
    const sectorOptions = selectedSector.expandsTo.map(id =>
      options[model].find(o => o.value === id)
    );

    return sectorOptions;
  }
);

const getExpandedLegendGasesSelected = createSelector(
  [getModelSelected, getOptions, getOptionsSelected, getShouldExpandGases],
  (modelSelected, options, selectedOptions, shouldExpandGases) => {
    const model = toPlural(modelSelected);
    if (!shouldExpandGases) return null;

    const selectedGas = selectedOptions.gasesSelected[0];
    const gasOptions = selectedGas.expandsTo.map(id => options[model].find(o => o.value === id));

    return gasOptions;
  }
);

export const getLegendDataOptions = createSelector(
  [getModelSelected, getOptions],
  (modelSelected, options) => {
    if (!options || !modelSelected || !options[toPlural(modelSelected)]) {
      return null;
    }
    return options[toPlural(modelSelected)];
  }
);

export const getLegendDataSelected = createSelector(
  [
    getModelSelected,
    getOptions,
    getOptionsSelected,
    getExpandedLegendRegionsSelected,
    getExpandedLegendSectorsSelected,
    getExpandedLegendGasesSelected
  ],
  (
    modelSelected,
    options,
    selectedOptions,
    expandedLegendRegionsSelected,
    expandedLegendSectorsSelected,
    expandedLegendGasesSelected
  ) => {
    const model = toPlural(modelSelected);
    const selectedModel = `${model}Selected`;
    if (!selectedOptions || !modelSelected || !selectedOptions[selectedModel] || !options) {
      return null;
    }

    let dataSelected = selectedOptions[selectedModel];

    if (expandedLegendRegionsSelected) {
      dataSelected = expandedLegendRegionsSelected;
    } else if (expandedLegendSectorsSelected) {
      dataSelected = expandedLegendSectorsSelected;
    } else if (expandedLegendGasesSelected) {
      dataSelected = expandedLegendGasesSelected;
    }

    return castArray(dataSelected).filter(c => c && !c.hideLegend);
  }
);

const getExpandedData = createSelector(
  [getData, getLegendDataSelected],
  (data, legendDataSelected) => {
    if (!legendDataSelected || !data || !data.length) return null;
    const othersOption = legendDataSelected.find(o => o.iso === 'OTHERS');
    if (!othersOption) return data;

    const expandedRegionISO = othersOption.expandedOptionValue;
    const expandedRegionData = data.find(d => d.iso_code3 === expandedRegionISO);

    const othEmByYear = {};
    const regionBelongsToOthers = d => othersOption.expandsTo.includes(d.iso_code3);

    // if expanded region like for example WORLD has it's own data line
    // then use that Total value to calculate Others value
    if (expandedRegionData) {
      const expandedCountriesISOCodes = legendDataSelected
        .filter(o => o.iso !== 'OTHERS')
        .map(o => o.iso);
      const exCountriesEmByYear = {};
      data.forEach(d => {
        if (expandedCountriesISOCodes.includes(d.iso_code3)) {
          d.emissions.forEach(e => {
            exCountriesEmByYear[e.year] = (exCountriesEmByYear[e.year] || 0) + e.value;
          });
        }
      });

      expandedRegionData.emissions.forEach(e => {
        othEmByYear[e.year] = e.value - exCountriesEmByYear[e.year];
      });
    } else {
      data.forEach(d => {
        if (regionBelongsToOthers(d)) {
          d.emissions.forEach(e => {
            othEmByYear[e.year] = (othEmByYear[e.year] || 0) + e.value;
          });
        }
      });
    }

    const othersData = {
      iso_code3: 'OTHERS',
      location: 'Others',
      emissions: Object.keys(othEmByYear).map(year => ({
        year: Number(year),
        value: othEmByYear[year]
      }))
    };

    return [...data.filter(d => !regionBelongsToOthers(d)), othersData];
  }
);

const getSortedData = createSelector(getExpandedData, data => {
  if (!data || isEmpty(data)) return null;
  return sortEmissionsByValue(data);
});

const getYColumnOptions = createSelector([getLegendDataSelected], legendDataSelected => {
  if (!legendDataSelected) return null;
  const getYOption = columns =>
    columns &&
    columns.map(d => ({
      ...d,
      value: d && getYColumnValue(d.label)
    }));
  return uniqBy(getYOption(legendDataSelected), 'value');
});

const getCalculationData = createSelector([getWBData], data => {
  if (!data || isEmpty(data)) return null;
  const yearData = {};
  Object.keys(data).forEach(iso => {
    data[iso].forEach(d => {
      if (!yearData[d.year]) yearData[d.year] = {};
      yearData[d.year][iso] = { population: d.population, gdp: d.gdp };
    });
  });
  return yearData;
});

// some regions expands to underlying countries but also have
// their own aggregated data
const getRegionsWithOwnData = (regions, data) => {
  if (!regions || isEmpty(data)) return [];

  const regionsISOs = regions.map(r => r.iso_code3);
  const regionsISOsWithOwnData = uniq(
    data.filter(d => regionsISOs.includes(d.iso_code3)).map(d => d.iso_code3)
  );

  return regions.filter(r => regionsISOsWithOwnData.includes(r.iso_code3));
};

export const getChartData = createSelector(
  [
    getSortedData,
    getRegions,
    getModelSelected,
    getYColumnOptions,
    getMetricSelected,
    getCalculationData
  ],
  (data, regions, model, yColumnOptions, metric, calculationData) => {
    if (!data || !data.length || !model || !calculationData || !regions) return null;
    const yearValues = data[0].emissions.map(d => d.year);
    const metricField = {
      PER_CAPITA: 'population',
      PER_GDP: 'gdp'
    }[metric];
    const shouldHaveMetricData = !!metricField;

    const regionsWithOwnData = getRegionsWithOwnData(regions, data);
    const regionWithOwnDataMembers = uniq(
      flatMap(regionsWithOwnData, r => r.members.map(m => m.iso_code3))
    );
    const memberOfRegionWithOwnData = d => regionWithOwnDataMembers.includes(d.iso_code3);

    const isBreakByRegions = model === 'regions';
    const groupByKey = isBreakByRegions ? 'location' : model;
    const groupedData = groupBy(data, groupByKey);

    const expandedData = column => {
      if (!column.expandsTo) return null;

      return flatMap(column.expandsTo, e => data.filter(d => d.iso_code3 === e)).filter(d => d);
    };

    const expandRegionToCountries = iso => {
      const region = regions.find(r => r.iso_code3 === iso);
      if (!region || !region.members) return iso;
      return flatMap(region.members, expandRegionToCountries);
    };
    const expandRegionsToCountries = isos => uniq(flatMap(isos, expandRegionToCountries));

    const getMetricData = (year, region, column) => {
      const getMetricForYearAndRegion = (y, r) =>
        metricField &&
        calculationData &&
        calculationData[y] &&
        calculationData[y][r] &&
        calculationData[y][r][metricField];
      let metricData = getMetricForYearAndRegion(year, region);

      // if no metric data for expandable column then use expanded regions to
      // calculate metric data
      if (!metricData && column.expandsTo && column.expandsTo.length) {
        const expandedCountries = expandRegionsToCountries(column.expandsTo);
        metricData = expandedCountries.reduce(
          (acc, iso) => acc + (getMetricForYearAndRegion(year, iso) || 0),
          0
        );
      }

      // GDP is in dollars and we want to display it in million dollars
      if (metricField === 'gdp' && metricData) metricData /= 1000000;

      return metricData;
    };

    const dataParsed = yearValues.map(year => {
      const yItems = {};

      yColumnOptions.forEach(column => {
        const dataForColumn = groupedData[column.label] || expandedData(column) || [];
        const key = column.value;
        let totalValue = null;
        let totalMetric = null;

        dataForColumn.forEach(d => {
          if (!isBreakByRegions && memberOfRegionWithOwnData(d)) return;

          const yearEmissions = d.emissions.find(e => e.year === year);
          const metricRatio = shouldHaveMetricData ? getMetricData(year, d.iso_code3, column) : 1;

          if (yearEmissions && metricRatio) {
            totalValue += yearEmissions.value;
            totalMetric = shouldHaveMetricData ? totalMetric + metricRatio : 1;
          }
        });

        if (totalValue !== null && totalMetric !== null) {
          yItems[key] = totalValue * (DATA_SCALE / totalMetric);
        }
      });

      return {
        x: year,
        ...yItems
      };
    });

    // if there is no value for any legend item
    // remove those element from the start and the end of chart
    // leave those in the middle
    const trimWithNoData = dataToTrim => {
      const indexesToString = dataToTrim
        .map((d, idx) => (Object.keys(d).length > 1 ? idx : '_'))
        .join(',')
        .replace(/_,/g, '')
        .trim();
      const middleIndexes = indexesToString.split(',');
      const firstNotEmptyIndex = Number(middleIndexes[0]);
      const lastNotEmptyIndex = Number(middleIndexes[middleIndexes.length - 1]);

      return dataToTrim.filter((_d, idx) => idx >= firstNotEmptyIndex && idx <= lastNotEmptyIndex);
    };

    return trimWithNoData(dataParsed);
  }
);

export const getChartDomain = createSelector([getChartData], data => {
  if (!data) return null;
  return {
    x: setXAxisDomain(),
    y: setYAxisDomain()
  };
});

// variable that caches chart elements assigned color
// to avoid element color changing when the chart is updated
let colorThemeCache = {
  yOthers: { stroke: OTHER_COLOR, fill: OTHER_COLOR }
};

const getColorPalette = columns => {
  if (columns.length <= 10) return NEW_CHART_COLORS_BASIC;
  if (columns.length <= 20) return NEW_CHART_COLORS;
  return NEW_CHART_COLORS_EXTENDED;
};

const getUnit = metric => {
  let unit = DEFAULT_AXES_CONFIG.yLeft.unit;
  if (metric === 'PER_GDP') {
    unit = `${unit}/ million $ GDP`;
  } else if (metric === 'PER_CAPITA') {
    unit = `${unit} per capita`;
  }
  return unit;
};

export const getChartConfig = createSelector(
  [getModelSelected, getMetricSelected, getYColumnOptions],
  (model, metric, yColumns) => {
    if (!model || !yColumns) return null;
    const colorPalette = getColorPalette(yColumns);
    colorThemeCache = getThemeConfig(yColumns, colorPalette, colorThemeCache);
    const tooltip = getTooltipConfig(yColumns.filter(c => c && !c.hideLegend));
    const unit = getUnit(metric);
    return {
      axes: {
        ...DEFAULT_AXES_CONFIG,
        yLeft: {
          ...DEFAULT_AXES_CONFIG.yLeft,
          unit,
          suffix: 't'
        }
      },
      theme: colorThemeCache,
      tooltip,
      animation: false,
      columns: {
        x: [
          {
            label: 'year',
            value: 'x'
          }
        ],
        y: yColumns
      }
    };
  }
);

export const getTableData = createSelector(
  [getChartData, getMetricSelected, getModelSelected, getYColumnOptions],
  (data, metric, model, yColumnOptions) => {
    if (!data || !model || !data.length || !yColumnOptions) return null;

    const formatValue = value => value && Number(value.toFixed(2));
    const unit = getUnit(metric);

    const pivot = yColumnOptions.map(c => ({
      [GHG_TABLE_HEADER[model]]: c.label,
      unit,
      ...data.reduce(
        (acc, d) => ({
          ...acc,
          [String(d.x)]: formatValue(d[c.value]) // year: value
        }),
        {}
      )
    }));

    return pivot;
  }
);

export const getLoading = createSelector(
  [getChartConfig, state => state.ghgEmissionsMeta, state => state.emissions],
  (chartConfig, meta, data) =>
    (meta && meta.loading) || (data && data.loading) || !chartConfig || false
);

export const getHideRemoveOptions = createSelector(
  [getShouldExpandRegions, getShouldExpandSectors, getShouldExpandGases],
  (shouldExpandRegions, shouldExpandSectors, shouldExpandGases) =>
    shouldExpandRegions || shouldExpandSectors || shouldExpandGases
);
<|endoftext|>


<commit_before>module('lively.ide.tools.TextEditor').requires('lively.persistence.BuildSpec').toRun(function() {

// currently used for filesystem read/write
module("lively.ide.CommandLineInterface").load();

var defaultExtent = lively.Config.defaultTextEditorExtent,
    extent = pt(defaultExtent[0], defaultExtent[1]) || pt(500, 400);

lively.BuildSpec('lively.ide.tools.TextEditor', {
    _Extent: extent,
    className: "lively.morphic.Window",
    contentOffset: lively.pt(4.0,22.0),
    state: "expanded",
    draggingEnabled: true,
    layout: {adjustForNewBounds: true},
    submorphs: [{
        _BorderWidth: 1,
        _Position: lively.pt(4.0,22.0),
        className: "lively.morphic.Box",
        name: 'container',
        layout: {
            adjustForNewBounds: true,
            resizeHeight: true,
            resizeWidth: true
        },
        onWindowGetsFocus: function onWindowGetsFocus() {
            this.get('editor').focus();
        },
        submorphs: [
        lively.BuildSpec('lively.ide.tools.CommandLine').customize({
            _Extent: extent.withY(18),
            clearOnInput: false,
            name: 'urlText',
            layout: {resizeWidth: true},
        }), {
            className: "lively.morphic.Button",
            name: 'saveButton',
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(0.0,30.0),
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "save",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(300.0,30.0),
            className: "lively.morphic.Button",
            name: 'loadButton',
            doNotCopyProperties: [],
            doNotSerialize: [],
            droppingEnabled: false,
            grabbingEnabled: false,
            isPressed: false,
            label: "load",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(600.0,30.0),
            className: "lively.morphic.Button",
            name: 'removeButton',
            // droppingEnabled: false,
            // grabbingEnabled: false,
            label: "remove",
            layout: {
                scaleHorizontal: true
            }
        },{
            _FontFamily: "Monaco",
            className: "lively.morphic.CodeEditor",
            name: 'editor',
            evalEnabled: false,
            grabbingEnabled: false,
            layout: {
                resizeHeight: true,
                resizeWidth: true
            },
            sourceModule: "lively.ide.CodeEditor",
            textString: ""
        }]
    }],
    titleBar: "TextEditor",
    onFromBuildSpecCreated: function onFromBuildSpecCreated() {
        $super();
        var win = this,
            editor = win.get('editor'),
            loadButton = win.get('loadButton'),
            saveButton = win.get('saveButton'),
            removeButton = win.get('removeButton'),
            container = win.get('container'),
            urlText = this.get('urlText');
        container.setBounds(win.innerBounds().insetByRect(lively.rect(win.contentOffset, pt(4,4))));
        urlText.setBounds(container.getExtent().withY(18).extentAsRectangle());
        var third = container.getExtent().x/3, pos = pt(0,18);
        loadButton.setBounds(pos.extent(pt(third, 22)));
        saveButton.setBounds(pos.withX(third).extent(pt(third, 22)));
        removeButton.setBounds(pos.withX(2*third).extent(pt(third, 22)));
        editor.setBounds(lively.rect(loadButton.bounds().bottomLeft(), container.innerBounds().bottomRight()));
    },
    connectionRebuilder: function connectionRebuilder() {
        var urlText = this.get('urlText'),
            editor = this.get('editor'),
            loadButton = this.get('loadButton'),
            saveButton = this.get('saveButton'),
            removeButton = this.get('removeButton'),
            container = this.get('container');
        lively.bindings.connect(urlText, 'savedTextString', this, 'loadFile');
        lively.bindings.connect(loadButton, 'fire', this, 'loadFile');
        lively.bindings.connect(removeButton, 'fire', this, 'removeFile');
        lively.bindings.connect(saveButton, 'fire', this, 'saveFile');
        lively.bindings.connect(editor, 'savedTextString', this, 'saveFile');
        lively.bindings.connect(this, 'contentLoaded', editor, 'textString');
        lively.bindings.connect(this, 'contentLoaded', this, 'gotoLocationLine');
        lively.bindings.connect(this, 'contentLoaded', editor, 'setTextMode', {updater: function($upd) {
            var ext = this.sourceObj.getFileExtension();
            switch(ext) {
                case "css": $upd("css"); return;
                case "diff": $upd("diff"); return;
                case "html": $upd("html"); return;
                case "js": $upd("javascript"); return;
                case "md": $upd("markdown"); return;
                case "sh": $upd("sh"); return;
                case "xml": $upd("xml"); return;
                case "svg": $upd("svg"); return;
                case "el": $upd("lisp"); return;
                case "clj": $upd("clojure"); return;
                default: $upd("text");
            }
        }});
    },
    getLine: function getLine() {
        var string = this.get('urlText').textString,
            match = string.match(/:([0-9]+)$/);
        return match && Number(match[1]);
    },
    gotoLocationLine: function gotoLocationLine() {
        var line = this.getLine();
        if (!line) return;
        var editor = this.get('editor');
        editor.scrollToRow(line);
        editor.setCursorPosition(pt(0, line-1));
    },
    getLocation: function getLocation(asString) {
        var string = this.get('urlText').textString;
        string = string.replace(/:[0-9]+$/, '');
        if (asString) return string;
        try {
            return new URL(string);
        } catch(e) {
            return string;
        }
    },
    getFileExtension: function getFileExtension() {
        return this.getLocation(true).split('.').last();
    },
    getWebResource: function getWebResource() {
        var loc = this.getLocation();
        return loc.isURL && loc.asWebResource();
    },
    loadFile: function loadFile() {
        var location = this.getLocation();
        this.setTitle(String(location));
        if (location.isURL) {
            this.loadFileNetwork();
        } else {
            this.loadFileFileSystem();
        }
    },
    loadFileFileSystem: function loadFileFileSystem() {
        var path = this.getLocation(true);
        lively.ide.CommandLineInterface.readFile(path, {}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            if (err) { this.message(Strings.format("Could not read file.\nError: %s", err)); return; }
            lively.bindings.signal(this, 'contentLoaded', cmd.getStdout());
        }.bind(this));
    },
    loadFileNetwork: function loadFileNetwork() {
        var webR = this.getWebResource();
        connect(webR, 'content', this, 'contentLoaded');
        webR.beAsync().forceUncached().get();
    },
    saveFile: function saveFile() {
        var loc = this.getLocation();
        if (loc.isURL) {
            this.saveFileNetwork();
        } else {
            this.saveFileFileSystem();
        }
    },
    saveFileFileSystem: function saveFileFileSystem() {
        var path = this.getLocation(true), content = this.get('editor').textString;
        lively.ide.CommandLineInterface.writeFile(path, {content: content}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            if (err) { this.message(Strings.format("Could not write file.\nError: %s", err), Color.red); return; }
            this.message("File saved successfully.", Color.green);
            lively.bindings.signal(this, 'contentStored');
        }.bind(this));
    },
    saveFileNetwork: function saveFileNetwork() {
        var webR = this.getWebResource();
        webR.statusMessage(webR.getURL() + ' saved', webR.getURL() + ' could not be saved!');
        webR.beAsync().put(this.get('editor').textString);
    },
    removeFile: function removeFile() {
        var webR = this.getWebResource();
        webR.statusMessage(webR.getURL() + ' removed', webR.getURL() + ' could not removed!');
        webR.beAsync().del();
    },
    openURL: function openURL(url) {
        this.get('urlText').textString = String(url);
        this.loadFile();
    },
    message: function(/*msg, color, ...*/) { var ed = this.get('editor'); ed.setStatusMessage.apply(ed,arguments); },
    onKeyDown: function onKeyDown(evt) {
        var keys = evt.getKeyString();
        if (keys === 'F1') {
            this.get('urlText').focus(); evt.stop(); return true;
        } else if (keys === 'F2') {
            this.get('editor').focus(); evt.stop(); return true;
        } else if (keys === "Command-U") {
            $world.confirm('Revert input / reload file?', function(input) {
                if (!input) { alertOK('Rever canceled'); return; }
                this.loadFile();
            }.bind(this));
            return true;
        }
        return $super(evt);
    }
});

}) // end of module<commit_msg>texteditor: automatically use R textmode when opening *.r files<commit_after>module('lively.ide.tools.TextEditor').requires('lively.persistence.BuildSpec').toRun(function() {

// currently used for filesystem read/write
module("lively.ide.CommandLineInterface").load();

var defaultExtent = lively.Config.defaultTextEditorExtent,
    extent = pt(defaultExtent[0], defaultExtent[1]) || pt(500, 400);

lively.BuildSpec('lively.ide.tools.TextEditor', {
    _Extent: extent,
    className: "lively.morphic.Window",
    contentOffset: lively.pt(4.0,22.0),
    state: "expanded",
    draggingEnabled: true,
    layout: {adjustForNewBounds: true},
    submorphs: [{
        _BorderWidth: 1,
        _Position: lively.pt(4.0,22.0),
        className: "lively.morphic.Box",
        name: 'container',
        layout: {
            adjustForNewBounds: true,
            resizeHeight: true,
            resizeWidth: true
        },
        onWindowGetsFocus: function onWindowGetsFocus() {
            this.get('editor').focus();
        },
        submorphs: [
        lively.BuildSpec('lively.ide.tools.CommandLine').customize({
            _Extent: extent.withY(18),
            clearOnInput: false,
            name: 'urlText',
            layout: {resizeWidth: true},
        }), {
            className: "lively.morphic.Button",
            name: 'saveButton',
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(0.0,30.0),
            droppingEnabled: false,
            grabbingEnabled: false,
            label: "save",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(300.0,30.0),
            className: "lively.morphic.Button",
            name: 'loadButton',
            doNotCopyProperties: [],
            doNotSerialize: [],
            droppingEnabled: false,
            grabbingEnabled: false,
            isPressed: false,
            label: "load",
            layout: {
                scaleHorizontal: true
            },
            sourceModule: "lively.morphic.Widgets",
            submorphs: [],
            toggle: false,
            value: false
        },{
            _BorderColor: Color.rgb(189,190,192),
            _BorderWidth: 1,
            _Extent: lively.pt(300.0,30.0),
            _Position: lively.pt(600.0,30.0),
            className: "lively.morphic.Button",
            name: 'removeButton',
            // droppingEnabled: false,
            // grabbingEnabled: false,
            label: "remove",
            layout: {
                scaleHorizontal: true
            }
        },{
            _FontFamily: "Monaco",
            className: "lively.morphic.CodeEditor",
            name: 'editor',
            evalEnabled: false,
            grabbingEnabled: false,
            layout: {
                resizeHeight: true,
                resizeWidth: true
            },
            sourceModule: "lively.ide.CodeEditor",
            textString: ""
        }]
    }],
    titleBar: "TextEditor",
    onFromBuildSpecCreated: function onFromBuildSpecCreated() {
        $super();
        var win = this,
            editor = win.get('editor'),
            loadButton = win.get('loadButton'),
            saveButton = win.get('saveButton'),
            removeButton = win.get('removeButton'),
            container = win.get('container'),
            urlText = this.get('urlText');
        container.setBounds(win.innerBounds().insetByRect(lively.rect(win.contentOffset, pt(4,4))));
        urlText.setBounds(container.getExtent().withY(18).extentAsRectangle());
        var third = container.getExtent().x/3, pos = pt(0,18);
        loadButton.setBounds(pos.extent(pt(third, 22)));
        saveButton.setBounds(pos.withX(third).extent(pt(third, 22)));
        removeButton.setBounds(pos.withX(2*third).extent(pt(third, 22)));
        editor.setBounds(lively.rect(loadButton.bounds().bottomLeft(), container.innerBounds().bottomRight()));
    },
    connectionRebuilder: function connectionRebuilder() {
        var urlText = this.get('urlText'),
            editor = this.get('editor'),
            loadButton = this.get('loadButton'),
            saveButton = this.get('saveButton'),
            removeButton = this.get('removeButton'),
            container = this.get('container');
        lively.bindings.connect(urlText, 'savedTextString', this, 'loadFile');
        lively.bindings.connect(loadButton, 'fire', this, 'loadFile');
        lively.bindings.connect(removeButton, 'fire', this, 'removeFile');
        lively.bindings.connect(saveButton, 'fire', this, 'saveFile');
        lively.bindings.connect(editor, 'savedTextString', this, 'saveFile');
        lively.bindings.connect(this, 'contentLoaded', editor, 'textString');
        lively.bindings.connect(this, 'contentLoaded', this, 'gotoLocationLine');
        lively.bindings.connect(this, 'contentLoaded', editor, 'setTextMode', {updater: function($upd) {
            var ext = this.sourceObj.getFileExtension();
            switch(ext) {
                case "r": $upd("r"); return;
                case "css": $upd("css"); return;
                case "diff": $upd("diff"); return;
                case "html": $upd("html"); return;
                case "js": $upd("javascript"); return;
                case "md": $upd("markdown"); return;
                case "sh": $upd("sh"); return;
                case "xml": $upd("xml"); return;
                case "svg": $upd("svg"); return;
                case "el": $upd("lisp"); return;
                case "clj": $upd("clojure"); return;
                default: $upd("text");
            }
        }});
    },
    getLine: function getLine() {
        var string = this.get('urlText').textString,
            match = string.match(/:([0-9]+)$/);
        return match && Number(match[1]);
    },
    gotoLocationLine: function gotoLocationLine() {
        var line = this.getLine();
        if (!line) return;
        var editor = this.get('editor');
        editor.scrollToRow(line);
        editor.setCursorPosition(pt(0, line-1));
    },
    getLocation: function getLocation(asString) {
        var string = this.get('urlText').textString;
        string = string.replace(/:[0-9]+$/, '');
        if (asString) return string;
        try {
            return new URL(string);
        } catch(e) {
            return string;
        }
    },
    getFileExtension: function getFileExtension() {
        return this.getLocation(true).split('.').last();
    },
    getWebResource: function getWebResource() {
        var loc = this.getLocation();
        return loc.isURL && loc.asWebResource();
    },
    loadFile: function loadFile() {
        var location = this.getLocation();
        this.setTitle(String(location));
        if (location.isURL) {
            this.loadFileNetwork();
        } else {
            this.loadFileFileSystem();
        }
    },
    loadFileFileSystem: function loadFileFileSystem() {
        var path = this.getLocation(true);
        lively.ide.CommandLineInterface.readFile(path, {}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            if (err) { this.message(Strings.format("Could not read file.\nError: %s", err)); return; }
            lively.bindings.signal(this, 'contentLoaded', cmd.getStdout());
        }.bind(this));
    },
    loadFileNetwork: function loadFileNetwork() {
        var webR = this.getWebResource();
        connect(webR, 'content', this, 'contentLoaded');
        webR.beAsync().forceUncached().get();
    },
    saveFile: function saveFile() {
        var loc = this.getLocation();
        if (loc.isURL) {
            this.saveFileNetwork();
        } else {
            this.saveFileFileSystem();
        }
    },
    saveFileFileSystem: function saveFileFileSystem() {
        var path = this.getLocation(true), content = this.get('editor').textString;
        lively.ide.CommandLineInterface.writeFile(path, {content: content}, function(cmd) {
            var err = cmd.getCode() && cmd.getStderr();
            if (err) { this.message(Strings.format("Could not write file.\nError: %s", err), Color.red); return; }
            this.message("File saved successfully.", Color.green);
            lively.bindings.signal(this, 'contentStored');
        }.bind(this));
    },
    saveFileNetwork: function saveFileNetwork() {
        var webR = this.getWebResource();
        webR.statusMessage(webR.getURL() + ' saved', webR.getURL() + ' could not be saved!');
        webR.beAsync().put(this.get('editor').textString);
    },
    removeFile: function removeFile() {
        var webR = this.getWebResource();
        webR.statusMessage(webR.getURL() + ' removed', webR.getURL() + ' could not removed!');
        webR.beAsync().del();
    },
    openURL: function openURL(url) {
        this.get('urlText').textString = String(url);
        this.loadFile();
    },
    message: function(/*msg, color, ...*/) { var ed = this.get('editor'); ed.setStatusMessage.apply(ed,arguments); },
    onKeyDown: function onKeyDown(evt) {
        var keys = evt.getKeyString();
        if (keys === 'F1') {
            this.get('urlText').focus(); evt.stop(); return true;
        } else if (keys === 'F2') {
            this.get('editor').focus(); evt.stop(); return true;
        } else if (keys === "Command-U") {
            $world.confirm('Revert input / reload file?', function(input) {
                if (!input) { alertOK('Rever canceled'); return; }
                this.loadFile();
            }.bind(this));
            return true;
        }
        return $super(evt);
    }
});

}) // end of module<|endoftext|>