<commit_before>var client = {
	server: document.domain,
	status: {
		connection: false,
		pastDisconnect: false
	},
	focusedChannel: "",
	channels: [],
	nickname: "",
	highlights: [],
	channelList: ""
};

var socket = io.connect('http://' + client.server + ':4848', {
	'reconnect': true,
	'reconnection delay': 500
});

var select = function (selectors) {
	if (selectors.indexOf(',') != -1) {
		return document.querySelectorAll(selectors);
	} else {
		return document.querySelector(selectors);
	}
};

/**
 * Socket.io ON list:
 *	connect
 *	disconnect
 *	recieveMessage
 *		[to, from, message]
 *		Recieve a message from IRC.
 *	initialInfo
 *	ircInfo
 *
 * Socket.io EMIT list:
 *  sendMessage
 *		[channel, message]
 *		Sends a message to a channel.
 *  sendCommand
 *		{type, content}
 *		Send a command.
**/

socket.on('connect', function () {
	client.status.connection = true;

	select('#sidebar footer span').style.backgroundColor = '#3C9067';
	select('#sidebar footer span').innerHTML = "Connected";

	console.log("Connected to backend.");
});

socket.on('disconnect', function () {
	client.status.connection = false;
	client.status.pastDisconnect = true;

	select('#sidebar footer span').style.backgroundColor = "#903C3C";
	select('#sidebar footer span').innerHTML = "Disconnected";

	console.warn("Lost connection to backend.");
});

// IRC
socket.on('initialInfo', function (data) {
	client.nickname = data;
	client.highlights[0] = client.nickname;
});

socket.on('ircInfo', function (data) {
	client.channels = data;
	client.channelList = Object.keys(client.channels);

	select('#sidebar > ul').innerHTML = '';
	function updateChannelMenu (element, index) {
		select('#sidebar > ul').insertAdjacentHTML('beforeend', '
' + element + '
');
	}

	client.channelList.forEach(updateChannelMenu);

	if (client.focusedChannel === '') {
		client.focusedChannel = client.channelList[0].toLowerCase();
	}

	select('#sidebar > ul li:nth-of-type(1)').classList.add('focusedChannel');
	select('#channelConsole header input').value = '';
	select('#channelConsole header input').value = client.channels[client.focusedChannel].topic;
	select('#users > ul').innerHTML = '';

	// TODO: Make this organize users based on their ... permissions? I can't remember what it's called I didn't sleep last night sorry.
	var _userList = [],
		_opCount = 0;

	for (var k in client.channels[client.focusedChannel].users) {
		_userList.push(k);
	}

	for (var i = 0; i < _userList.length; i++) {
		select('#users ul').insertAdjacentHTML('beforeend', '
' + client.channels[client.focusedChannel].users[_userList[i]] + '' + _userList[i] + '
');

		// Get the op count.
		if (client.channels[client.focusedChannel].users[_userList[i]] === "@" || client.channels[client.focusedChannel].users[_userList[i]] === "~") {
			_opCount = _opCount+=1;
		}
	}

	// Get user count
	select('#users header p').innerHTML = '';
	select('#users header p').innerHTML = _opCount + " ops, " + Object.keys(client.channels[client.focusedChannel].users).length + " total";
});

for (var i = 0, len = select('#sidebar > ul').children.length; i < len; i++) {
	(function(index){
		select('#sidebar > ul').children[i].onclick = function(){
			client.focusedChannel = client.channelList[index].toLowerCase();

			select('#channelConsole header input').value = client.channels[client.channelList[index]].topic;
			select('sidebar > ul li').classList.remove('focusedChannel');
			select('#sidebar > ul li:nth-of-type(' + (index+=1) + ')').classList.add('focusedChannel');
			select('#users ul').innerHTML = '';

			// TODO: Make this organize users based on their ... permissions? I can't remember what it's called I didn't sleep last night sorry.

			// Set up userlist.
			var _channel = client.channels[client.focusedChannel],
				_userList = [],
				_opCount = 0,
				_users = _channel.users;

			for (var k in _users) {
				_userList.push(k);
			}

			for (var i = 0; i < _userList.length; i++) {
				select('#users ul').insertAdjacentHTML('beforeend', '
' + _users[_userList[i]] + '' + _userList[i] + '
');

				// Get the op count.
				if (_users[_userList[i]] === "@" || _users[_userList[i]] === "~") {
					_opCount = _opCount+=1;
				}
			}

			// Show messages that are from the focused channel.
			select('#channelConsole output article[data-channel=' + client.focusedChannel + ']').style.display = '';
			// Hide messages that are not from the focused channel.
			select("#channelConsole output article:not([data-channel='" + client.focusedChannel + "'])").style.display = 'none';

			// Get user count
			select('#users header p').innerHTML = '';
			select('#users header p').innerHTML = _opCount + " ops, " + Object.keys(_channel.users).length + " total";
		};
	})(i);
}

function displayMessage (data) {
	var message = data.message
		// Filter mean characters out and replace them with nice ones. We don't want mean characters.
		.replace(/&/g, "&")
		.replace(/"/g, '"')
		.replace(/'/g, "'")
		.replace(/</g, "<")
		.replace(/>/g, ">"),
		scrollInfoView,
		// Create the timestamp.
		messageTime = new Date(),
		timestamp = "[" + ("0" + messageTime.getHours()).slice(-2) + ":" + ("0" + messageTime.getMinutes()).slice(-2) + ":" + ("0" + messageTime.getSeconds()).slice(-2) + "]",
		highlightMessageTypes = ["message", "action", "notice"],
		isInHighlightList = false;

	// Linkify raw links.
	function linkify(input) {
		var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
		return input.replace(exp, "$1");
	}

	message = linkify(message);

	for (var i = 0; i < highlightMessageTypes.length && !isInHighlightList; i+=1) {
		if (highlightMessageTypes[i] == data.messageType) {
			isInHighlightList = true;
		}
	}

	if (isInHighlightList) {
		var highlightNick = function (name, input) {
			var exp = new RegExp('\\b(' + name + ')', 'ig');
			return input.replace(exp, '$1');
		};

		for (var i = 0; i < client.highlights.length; i++) {
			message = highlightNick(client.highlights[i], message);
		}
	}

	// If there is no specified channel just use the one the client is currently focused on.
	if (typeof data.channel === 'undefined') {
		data.channel = client.focusedChannel;
	}

	// If scrolled at the bottom set scrollIntView as true.
	if (select('#channelConsole output').scrollHeight - select('#channelConsole output').scrollTop === select('#channelConsole output').clientHeight) {
		scrollInfoView = true;
	}

	select('#channelConsole output').insertAdjacentHTML('beforeend', '
<time>' + timestamp + '</time>' + data.head + '
' + message + '

');

	if (select("#channelConsole output article:not([data-channel='" + client.focusedChannel + "'])")) {
		select("#channelConsole output article:not([data-channel='" + client.focusedChannel + "'])").style.display = 'none';
	}

	//Scroll to bottom unless the user is scrolled up
	if (scrollInfoView) {
		select('#channelConsole output').scrollTop = select('#channelConsole output').scrollHeight;
	}
}

socket.on('recieveMessage', function (data) {
	switch (data.type) {
		case "message":
			displayMessage({
				messageType: "message",
				head: data.nick,
				channel: data.channel,
				message: data.message
			});
			break;
		case "join":
			displayMessage({
				messageType: "join",
				head: "*",
				channel: data.channel,
				message: data.nick + " (" + data.info.host + ") has joined " + data.channel
			});
			break;
		case "part":
			displayMessage({
				messageType: "part",
				head: "*",
				channel: data.channel,
				message: data.nick + " (" + data.info.host + ") has left " + data.channel
			});
			break;
		case "quit":
			displayMessage({
				messageType: "quit",
				head: "*",
				channel: data.channel,
				message: data.nick + " (" + data.info.host + ") has quit " + data.channels + " (" + data.reason + ")"
			});
			break;
		case "notice":
			displayMessage({
				messageType: "notice",
				head: "-" + data.nick + "-",
				channel: data.channel,
				message: data.message
			});
			break;
	}
});

var irc = {
	sendMessage: function (data) {
		if (data === '') {
			return;
		} else if (data.substring(0, 1) != "/") {
			// It's not a command.
			socket.emit('sendMessage', [client.focusedChannel, data]);
			// Display it in the client.
			displayMessage({
				messageType: "message",
				head: client.nickname,
				message: data
			});
		} else {
			// It's a command.
			data = data.substring(1, data.length);

			var command = data.split(" ")[0],
				_message = data.substring(command.length+=1, data.length),
				commandList = ['me', 'join', 'part', 'whois', 'notice'],
				commandFound = false;

			// Check to see if the command is in commandList.
			for (var i = 0; i < commandList.length && !commandFound; i++) {
				if (commandList[i] == command) {
					commandFound = true;
				}
			}

			// It's not a command.
			if (!commandFound) {
				displayMessage({
					messageType: "log",
					head: "**",
					message: 'Sorry, "' + command + '" is not a recognized command.'
				});
				return;
			}

			// It is a command so lets run it!
			switch (command) {
				case "me":
					socket.emit('sendCommand', {
						type: "me",
						channel: client.focusedChannel,
						content: _message
					});
					displayMessage({
						messageType: "action",
						head: "»",
						message: client.nickname + " " + _message
					});
					break;
				case "join":
					var _channels = _message.split(" ");
					for (var i = 0; i < _channels.length; i+=1) {
						socket.emit('sendCommand', {
							type: "join",
							content: _channels[i]
						});
					}
					break;
				case "part":
					var _channels = _message.split(" ");
					for (var i = 0; i < _channels.length; i+=1) {
						socket.emit('sendCommand', {
							type: "part",
							content: _channels[i]
						});
					}
					break;
				case "notice":
					socket.emit('sendCommand', {
						type: "notice",
						channel: client.focusedChannel,
						content: _message
					});
					displayMessage({
						messageType: "notice",
						head: "-" + client.nickname + "-",
						message: client.nickname + " " + _message
					});
					break;
			}
		}

		select('#channelConsole footer input').value = "";
	}
};

// Press enter in chat box
select('#channelConsole footer input').onkeydown = function (event) {
	switch (event.which) {
		case 9: // Tab
			event.preventDefault();
			break;
		case 13:
			irc.sendMessage(select('#channelConsole footer input').value);
			break;
	}
};

select('#channelConsole footer button').onclick = function () {
	irc.sendMessage(select('#channelConsole footer input').value);
};
<commit_msg>Added user list sorting thanks to Magisun<commit_after>var client = {
	server: document.domain,
	status: {
		connection: false,
		pastDisconnect: false
	},
	focusedChannel: "",
	channels: [],
	nickname: "",
	highlights: [],
	channelList: ""
};

var socket = io.connect('http://' + client.server + ':4848', {
	'reconnect': true,
	'reconnection delay': 500
});

var select = function (selectors) {
	if (selectors.indexOf(',') != -1) {
		return document.querySelectorAll(selectors);
	} else {
		return document.querySelector(selectors);
	}
};

/**
 * Socket.io ON list:
 *	connect
 *	disconnect
 *	recieveMessage
 *		[to, from, message]
 *		Recieve a message from IRC.
 *	initialInfo
 *	ircInfo
 *
 * Socket.io EMIT list:
 *  sendMessage
 *		[channel, message]
 *		Sends a message to a channel.
 *  sendCommand
 *		{type, content}
 *		Send a command.
**/

socket.on('connect', function () {
	client.status.connection = true;

	select('#sidebar footer span').style.backgroundColor = '#3C9067';
	select('#sidebar footer span').innerHTML = "Connected";

	console.log("Connected to backend.");
});

socket.on('disconnect', function () {
	client.status.connection = false;
	client.status.pastDisconnect = true;

	select('#sidebar footer span').style.backgroundColor = "#903C3C";
	select('#sidebar footer span').innerHTML = "Disconnected";

	console.warn("Lost connection to backend.");
});

// IRC
socket.on('initialInfo', function (data) {
	client.nickname = data;
	client.highlights[0] = client.nickname;
});

function channelSetup() {
	// Set up userlist.
	var _channel = client.channels[client.focusedChannel],
		_userList = [],
		_opCount = 0,
		_users = _channel.users;

	for (var k in _users) {
		_userList.push(k);
	}

	_userList.sort(function(a,b) {
		var rankString = "\r+~@";
		var rankA = rankString.indexOf(_users[a]),
			rankB = rankString.indexOf(_users[b]);

		var rankSort = rankA == rankB ? 0 : (rankA > rankB ? -1 : 1);
		if (rankSort == 0) {
			return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
		}
		return rankSort;
	});

	for (var i = 0; i < _userList.length; i++) {
		select('#users ul').insertAdjacentHTML('beforeend', '
' + _users[_userList[i]] + '' + _userList[i] + '
');

		// Get the op count.
		if (_users[_userList[i]] === "@" || _users[_userList[i]] === "~") {
			_opCount = _opCount+=1;
		}
	}

	// Get user count
	select('#users header p').innerHTML = '';
	select('#users header p').innerHTML = _opCount + " ops, " + Object.keys(_channel.users).length + " total";
}

socket.on('ircInfo', function (data) {
	client.channels = data;
	client.channelList = Object.keys(client.channels);

	select('#sidebar > ul').innerHTML = '';
	function updateChannelMenu (element, index) {
		select('#sidebar > ul').insertAdjacentHTML('beforeend', '
' + element + '
');
	}

	client.channelList.forEach(updateChannelMenu);

	if (client.focusedChannel === '') {
		client.focusedChannel = client.channelList[0].toLowerCase();
	}

	select('#sidebar > ul li:nth-of-type(1)').classList.add('focusedChannel');
	select('#channelConsole header input').value = '';
	select('#channelConsole header input').value = client.channels[client.focusedChannel].topic;
	select('#users > ul').innerHTML = '';

	channelSetup();
});

for (var i = 0, len = select('#sidebar > ul').children.length; i < len; i++) {
	(function(index){
		select('#sidebar > ul').children[i].onclick = function(){
			client.focusedChannel = client.channelList[index].toLowerCase();

			select('#channelConsole header input').value = client.channels[client.channelList[index]].topic;
			select('sidebar > ul li').classList.remove('focusedChannel');
			select('#sidebar > ul li:nth-of-type(' + (index+=1) + ')').classList.add('focusedChannel');
			select('#users ul').innerHTML = '';

			// Show messages that are from the focused channel.
			select('#channelConsole output article[data-channel=' + client.focusedChannel + ']').style.display = '';
			// Hide messages that are not from the focused channel.
			select("#channelConsole output article:not([data-channel='" + client.focusedChannel + "'])").style.display = 'none';

			channelSetup();
		};
	})(i);
}

function displayMessage (data) {
	var message = data.message
		// Filter mean characters out and replace them with nice ones. We don't want mean characters.
		.replace(/&/g, "&")
		.replace(/"/g, '"')
		.replace(/'/g, "'")
		.replace(/</g, "<")
		.replace(/>/g, ">"),
		scrollInfoView,
		// Create the timestamp.
		messageTime = new Date(),
		timestamp = "[" + ("0" + messageTime.getHours()).slice(-2) + ":" + ("0" + messageTime.getMinutes()).slice(-2) + ":" + ("0" + messageTime.getSeconds()).slice(-2) + "]",
		highlightMessageTypes = ["message", "action", "notice"],
		isInHighlightList = false;

	// Linkify raw links.
	function linkify(input) {
		var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
		return input.replace(exp, "$1");
	}

	message = linkify(message);

	for (var i = 0; i < highlightMessageTypes.length && !isInHighlightList; i+=1) {
		if (highlightMessageTypes[i] == data.messageType) {
			isInHighlightList = true;
		}
	}

	if (isInHighlightList) {
		var highlightNick = function (name, input) {
			var exp = new RegExp('\\b(' + name + ')', 'ig');
			return input.replace(exp, '$1');
		};

		for (var i = 0; i < client.highlights.length; i++) {
			message = highlightNick(client.highlights[i], message);
		}
	}

	// If there is no specified channel just use the one the client is currently focused on.
	if (typeof data.channel === 'undefined') {
		data.channel = client.focusedChannel;
	}

	// If scrolled at the bottom set scrollIntView as true.
	if (select('#channelConsole output').scrollHeight - select('#channelConsole output').scrollTop === select('#channelConsole output').clientHeight) {
		scrollInfoView = true;
	}

	select('#channelConsole output').insertAdjacentHTML('beforeend', '
<time>' + timestamp + '</time>' + data.head + '
' + message + '

');

	if (select("#channelConsole output article:not([data-channel='" + client.focusedChannel + "'])")) {
		select("#channelConsole output article:not([data-channel='" + client.focusedChannel + "'])").style.display = 'none';
	}

	//Scroll to bottom unless the user is scrolled up
	if (scrollInfoView) {
		select('#channelConsole output').scrollTop = select('#channelConsole output').scrollHeight;
	}
}

socket.on('recieveMessage', function (data) {
	switch (data.type) {
		case "message":
			displayMessage({
				messageType: "message",
				head: data.nick,
				channel: data.channel,
				message: data.message
			});
			break;
		case "join":
			displayMessage({
				messageType: "join",
				head: "*",
				channel: data.channel,
				message: data.nick + " (" + data.info.host + ") has joined " + data.channel
			});
			break;
		case "part":
			displayMessage({
				messageType: "part",
				head: "*",
				channel: data.channel,
				message: data.nick + " (" + data.info.host + ") has left " + data.channel
			});
			break;
		case "quit":
			displayMessage({
				messageType: "quit",
				head: "*",
				channel: data.channel,
				message: data.nick + " (" + data.info.host + ") has quit " + data.channels + " (" + data.reason + ")"
			});
			break;
		case "notice":
			displayMessage({
				messageType: "notice",
				head: "-" + data.nick + "-",
				channel: data.channel,
				message: data.message
			});
			break;
	}
});

var irc = {
	sendMessage: function (data) {
		if (data === '') {
			return;
		} else if (data.substring(0, 1) != "/") {
			// It's not a command.
			socket.emit('sendMessage', [client.focusedChannel, data]);
			// Display it in the client.
			displayMessage({
				messageType: "message",
				head: client.nickname,
				message: data
			});
		} else {
			// It's a command.
			data = data.substring(1, data.length);

			var command = data.split(" ")[0],
				_message = data.substring(command.length+=1, data.length),
				commandList = ['me', 'join', 'part', 'whois', 'notice'],
				commandFound = false;

			// Check to see if the command is in commandList.
			for (var i = 0; i < commandList.length && !commandFound; i++) {
				if (commandList[i] == command) {
					commandFound = true;
				}
			}

			// It's not a command.
			if (!commandFound) {
				displayMessage({
					messageType: "log",
					head: "**",
					message: 'Sorry, "' + command + '" is not a recognized command.'
				});
				return;
			}

			// It is a command so lets run it!
			switch (command) {
				case "me":
					socket.emit('sendCommand', {
						type: "me",
						channel: client.focusedChannel,
						content: _message
					});
					displayMessage({
						messageType: "action",
						head: "»",
						message: client.nickname + " " + _message
					});
					break;
				case "join":
					var _channels = _message.split(" ");
					for (var i = 0; i < _channels.length; i+=1) {
						socket.emit('sendCommand', {
							type: "join",
							content: _channels[i]
						});
					}
					break;
				case "part":
					var _channels = _message.split(" ");
					for (var i = 0; i < _channels.length; i+=1) {
						socket.emit('sendCommand', {
							type: "part",
							content: _channels[i]
						});
					}
					break;
				case "notice":
					socket.emit('sendCommand', {
						type: "notice",
						channel: client.focusedChannel,
						content: _message
					});
					displayMessage({
						messageType: "notice",
						head: "-" + client.nickname + "-",
						message: client.nickname + " " + _message
					});
					break;
			}
		}

		select('#channelConsole footer input').value = "";
	}
};

// Press enter in chat box
select('#channelConsole footer input').onkeydown = function (event) {
	switch (event.which) {
		case 9: // Tab
			event.preventDefault();
			break;
		case 13:
			irc.sendMessage(select('#channelConsole footer input').value);
			break;
	}
};

select('#channelConsole footer button').onclick = function () {
	irc.sendMessage(select('#channelConsole footer input').value);
};
<|endoftext|>


<commit_before>var mongoose = require('mongoose')
  , User = mongoose.model('User')
  , CarePlan = mongoose.model('CarePlan')
  , CareProvider = mongoose.model('CareProvider');

exports.join = function(req, res, next){
  var key = req.params.id;
  CarePlan.findOne({careProviders: {$elemMatch: {inviteKey: key}}}, function(error, plan){
    var careProvider = plan.careProviders.filter(function(elem){ return elem.inviteKey == self.careProvider.inviteKey})[0];
    if(error) return next(error);
    careProvider.userId = req.user.id;
    careProvider.inviteKey = null;
    careProvider.email = req.user.email;
    careProvider.name = req.user.name.full;
    carePlan.save(function(error, result){
      if(error) return next(error);
      res.redirect('/care_plans/'+plan.id);
    });
  });
}<commit_msg>Sharing now works!<commit_after>var mongoose = require('mongoose')
  , User = mongoose.model('User')
  , CarePlan = mongoose.model('CarePlan')
  , CareProvider = mongoose.model('CareProvider');

exports.join = function(req, res, next){
  var key = req.params.id;
  var self = this;
  CarePlan.findOne({careProviders: {$elemMatch: {inviteKey: key}}}, function(error, carePlan){
    if(error) return next(error);
    var careProvider = carePlan.careProviders.filter(function(elem){
      console.log(elem, key);
      return elem.inviteKey == key;
    })[0];
    careProvider.userId = req.user.id;
    careProvider.inviteKey = null;
    careProvider.email = req.user.email;
    careProvider.name = req.user.name.full;
    carePlan.save(function(error, result){
      if(error) return next(error);
      res.redirect('/care_plans/' + carePlan.id);
    });
  });
}<|endoftext|>


<commit_before>var dl = require('datalib'),
    axs = require('../scene/axis');

var ORIENT = {
  "x":      "bottom",
  "y":      "left",
  "top":    "top",
  "bottom": "bottom",
  "left":   "left",
  "right":  "right"
};

function parseAxes(model, spec, axes, group) {
  var config = model.config();
  (spec || []).forEach(function(def, index) {
    var updatedConfig = setConfig(config, def.type);
    axes[index] = axes[index] || axs(model, updatedConfig);
    parseAxis(updatedConfig, def, index, axes[index], group);
  });
}

function setConfig(config, defType) {
  var updatedConfig = {};
  updatedConfig.axis = {};
  var axisName = 'axis_' + defType;

  if (typeof config !== 'undefined') {
    if (config[axisName]) {
      for (var key in config[axisName]) {
        if (config[axisName].hasOwnProperty(key)) {
          updatedConfig.axis[key] = config[axisName][key];
        }
      }
    }

    for (var key in config.axis) {
      if (config.axis.hasOwnProperty(key) && (!(key in updatedConfig.axis))) {
        updatedConfig.axis[key] = config.axis[key];
      }
    }
  }
  return updatedConfig;
}

function parseAxis(config, def, index, axis, group) {
  // axis scale
  if (def.scale !== undefined) {
    axis.scale(group.scale(def.scale));
  }

  var scaleType = group.scale(def.scale).type;
  var grid = config.axis.grid;
  if (typeof grid === 'object') {
    if (scaleType in grid) {
      config.axis.grid = grid[scaleType];
    } else {
      config.axis.grid = grid['default'];
    }
  }

  // axis orientation
  axis.orient(def.orient || ORIENT[def.type]);
  // axis offset
  axis.offset(def.offset || 0);
  // axis layer
  axis.layer(def.layer || config.axis.layer || "front");
  // axis grid lines
  axis.grid(def.grid || config.axis.grid || false);
  // axis title
  axis.title(def.title || null);
  // axis title offset
  axis.titleOffset(def.titleOffset != null ?
    def.titleOffset : config.axis.titleOffset);
  // axis values
  axis.tickValues(def.values || null);
  // axis label formatting
  axis.tickFormat(def.format || null);
  axis.tickFormatType(def.formatType || null);
  // axis tick subdivision
  axis.tickSubdivide(def.subdivide || 0);
  // axis tick padding
  axis.tickPadding(def.tickPadding || config.axis.padding);

  // axis tick size(s)
  var size = [];
  if (def.tickSize !== undefined) {
    for (var i=0; i<3; ++i) size.push(def.tickSize);
  } else {
    var ts = config.axis.tickSize;
    size = [ts, ts, ts];
  }
  if (def.tickSizeMajor != null) size[0] = def.tickSizeMajor;
  if (def.tickSizeMinor != null) size[1] = def.tickSizeMinor;
  if ('tickSizeEnd' in config.axis) {
    size[2] = config.axis.tickSizeEnd;
  } else if (def.tickSizeEnd   != null) {
    size[2] = def.tickSizeEnd;
  }
  if (size.length) {
    axis.tickSize.apply(axis, size);
  }

  // axis tick count
  axis.tickCount(def.ticks || config.axis.ticks);

  // style properties
  var p = def.properties;
  if (p && p.ticks) {
    axis.majorTickProperties(p.majorTicks ?
      dl.extend({}, p.ticks, p.majorTicks) : p.ticks);
    axis.minorTickProperties(p.minorTicks ?
      dl.extend({}, p.ticks, p.minorTicks) : p.ticks);
  } else {
    axis.majorTickProperties(p && p.majorTicks || {});
    axis.minorTickProperties(p && p.minorTicks || {});
  }
  axis.tickLabelProperties(p && p.labels || {});
  axis.titleProperties(p && p.title || {});
  axis.gridLineProperties(p && p.grid || {});
  axis.domainProperties(p && p.axis || {});
}

module.exports = parseAxes;

parseAxes.schema = {
  "defs": {
    "axis": {
      "type": "object",
      "properties": {
        "type": {"enum": ["x", "y"]},
        "scale": {"type": "string"},
        "orient": {"enum": ["top", "bottom", "left", "right"]},
        "title": {"type": "string"},
        "titleOffset": {"type": "number"},
        "format": {"type": "string"},
        "formatType": {"enum": ["time", "utc", "string", "number"]},
        "ticks": {"type": "number"},
        "values": {
          "type": "array",
          "items": {"type": ["string", "number"]}
        },
        "subdivide": {"type": "number"},
        "tickPadding": {"type": "number"},
        "tickSize": {"type": "number"},
        "tickSizeMajor": {"type": "number"},
        "tickSizeMinor": {"type": "number"},
        "tickSizeEnd": {"type": "number"},
        "offset": {
          "oneOf": [{"type": "number"}, {
            "type": "object",
            "properties": {
              "scale": {"type": "string"},
              "value": {"type": ["string", "number"]}
            },
            "required": ["scale", "value"],
            "additionalProperties": false
          }]
        },
        "layer": {"enum": ["front", "back"], "default": "front"},
        "grid": {"type": "boolean"},
        "properties": {
          "type": "object",
          "properties": {
            "ticks": {"$ref": "#/defs/propset"},
            "majorTicks": {"$ref": "#/defs/propset"},
            "minorTicks": {"$ref": "#/defs/propset"},
            "labels": {"$ref": "#/defs/propset"},
            "title": {"$ref": "#/defs/propset"},
            "grid": {"$ref": "#/defs/propset"},
            "axis": {"$ref": "#/defs/propset"}
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false,
      "required": ["type", "scale"]
    }
  }
};
<commit_msg>Remove print statement<commit_after>var dl = require('datalib'),
    axs = require('../scene/axis');

var ORIENT = {
  "x":      "bottom",
  "y":      "left",
  "top":    "top",
  "bottom": "bottom",
  "left":   "left",
  "right":  "right"
};

function parseAxes(model, spec, axes, group) {
  var config = model.config();
  (spec || []).forEach(function(def, index) {
    var updatedConfig = setConfig(config, def.type);
    axes[index] = axes[index] || axs(model, updatedConfig);
    parseAxis(updatedConfig, def, index, axes[index], group);
  });
}

function setConfig(config, defType) {
  var updatedConfig = {};
  updatedConfig.axis = {};
  var axisName = 'axis_' + defType;

  if (typeof config !== 'undefined') {
    if (config[axisName]) {
      for (var key in config[axisName]) {
        if (config[axisName].hasOwnProperty(key)) {
          updatedConfig.axis[key] = config[axisName][key];
        }
      }
    }

    for (var key in config.axis) {
      if (config.axis.hasOwnProperty(key) && (!(key in updatedConfig.axis))) {
        updatedConfig.axis[key] = config.axis[key];
      }
    }
  }
  return updatedConfig;
}

function parseAxis(config, def, index, axis, group) {
  console.log(config);
  // axis scale
  if (def.scale !== undefined) {
    axis.scale(group.scale(def.scale));
  }

  var scaleType = group.scale(def.scale).type;
  var grid = config.axis.grid;
  if (typeof grid === 'object') {
    if (scaleType in grid) {
      config.axis.grid = grid[scaleType];
    } else {
      config.axis.grid = grid['default'];
    }
  }

  // axis orientation
  axis.orient(def.orient || ORIENT[def.type]);
  // axis offset
  axis.offset(def.offset || 0);
  // axis layer
  axis.layer(def.layer || config.axis.layer || "front");
  // axis grid lines
  axis.grid(def.grid || config.axis.grid || false);
  // axis title
  axis.title(def.title || null);
  // axis title offset
  axis.titleOffset(def.titleOffset != null ?
    def.titleOffset : config.axis.titleOffset);
  // axis values
  axis.tickValues(def.values || null);
  // axis label formatting
  axis.tickFormat(def.format || null);
  axis.tickFormatType(def.formatType || null);
  // axis tick subdivision
  axis.tickSubdivide(def.subdivide || 0);
  // axis tick padding
  axis.tickPadding(def.tickPadding || config.axis.padding);

  // axis tick size(s)
  var size = [];
  if (def.tickSize !== undefined) {
    for (var i=0; i<3; ++i) size.push(def.tickSize);
  } else {
    var ts = config.axis.tickSize;
    size = [ts, ts, ts];
  }
  if (def.tickSizeMajor != null) size[0] = def.tickSizeMajor;
  if (def.tickSizeMinor != null) size[1] = def.tickSizeMinor;
  if ('tickSizeEnd' in config.axis) {
    size[2] = config.axis.tickSizeEnd;
  } else if (def.tickSizeEnd   != null) {
    size[2] = def.tickSizeEnd;
  }
  if (size.length) {
    axis.tickSize.apply(axis, size);
  }

  // axis tick count
  axis.tickCount(def.ticks || config.axis.ticks);

  // style properties
  var p = def.properties;
  if (p && p.ticks) {
    axis.majorTickProperties(p.majorTicks ?
      dl.extend({}, p.ticks, p.majorTicks) : p.ticks);
    axis.minorTickProperties(p.minorTicks ?
      dl.extend({}, p.ticks, p.minorTicks) : p.ticks);
  } else {
    axis.majorTickProperties(p && p.majorTicks || {});
    axis.minorTickProperties(p && p.minorTicks || {});
  }
  axis.tickLabelProperties(p && p.labels || {});
  axis.titleProperties(p && p.title || {});
  axis.gridLineProperties(p && p.grid || {});
  axis.domainProperties(p && p.axis || {});
}

module.exports = parseAxes;

parseAxes.schema = {
  "defs": {
    "axis": {
      "type": "object",
      "properties": {
        "type": {"enum": ["x", "y"]},
        "scale": {"type": "string"},
        "orient": {"enum": ["top", "bottom", "left", "right"]},
        "title": {"type": "string"},
        "titleOffset": {"type": "number"},
        "format": {"type": "string"},
        "formatType": {"enum": ["time", "utc", "string", "number"]},
        "ticks": {"type": "number"},
        "values": {
          "type": "array",
          "items": {"type": ["string", "number"]}
        },
        "subdivide": {"type": "number"},
        "tickPadding": {"type": "number"},
        "tickSize": {"type": "number"},
        "tickSizeMajor": {"type": "number"},
        "tickSizeMinor": {"type": "number"},
        "tickSizeEnd": {"type": "number"},
        "offset": {
          "oneOf": [{"type": "number"}, {
            "type": "object",
            "properties": {
              "scale": {"type": "string"},
              "value": {"type": ["string", "number"]}
            },
            "required": ["scale", "value"],
            "additionalProperties": false
          }]
        },
        "layer": {"enum": ["front", "back"], "default": "front"},
        "grid": {"type": "boolean"},
        "properties": {
          "type": "object",
          "properties": {
            "ticks": {"$ref": "#/defs/propset"},
            "majorTicks": {"$ref": "#/defs/propset"},
            "minorTicks": {"$ref": "#/defs/propset"},
            "labels": {"$ref": "#/defs/propset"},
            "title": {"$ref": "#/defs/propset"},
            "grid": {"$ref": "#/defs/propset"},
            "axis": {"$ref": "#/defs/propset"}
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false,
      "required": ["type", "scale"]
    }
  }
};
<|endoftext|>


<commit_before>(function($) {

$("#realtime-widget").remove();
var container = $('
').css({
	position: "fixed",
	left: 0,
	bottom: 0,
	"background-color": "#ccc",
	width: "100%",
	"border-top": "solid 2px black"
}).appendTo(document.body)[0];

var height = 130;

var widget = $('').appendTo(container);
$(widget).css({
	position: "relative",
	height: height,
	"z-index": 0,
	width: "100%"
});

$('<button>real time</button>').click(function(ev) {
	$(widget).toggle();
}).css({
	position: "absolute", right: 0, bottom: "0px",
	"background": "none",
	"z-index": 2,
	"text-indent": -99,
	bottom: 0,
	border: "none",
	"background-repeat": "no-repeat",
	"background-image": "url(http://following.tiddlyspace.com/SiteIcon)"
}).appendTo(container);

})(jQuery);<commit_msg>add pointer cursor to toggle button<commit_after>(function($) {

$("#realtime-widget").remove();
var container = $('
').css({
	position: "fixed",
	left: 0,
	bottom: 0,
	"background-color": "#ccc",
	width: "100%",
	"border-top": "solid 2px black"
}).appendTo(document.body)[0];

var height = 130;

var widget = $('').appendTo(container);
$(widget).css({
	position: "relative",
	height: height,
	"z-index": 0,
	width: "100%"
});

$('<button>real time</button>').click(function(ev) {
	$(widget).toggle();
}).css({
	position: "absolute", right: 0, bottom: "0px",
	"background": "none",
	"z-index": 2,
	"text-indent": -99,
	bottom: 0,
	border: "none",
	"background-repeat": "no-repeat",
	cursor: "pointer",
	"background-image": "url(http://following.tiddlyspace.com/SiteIcon)"
}).appendTo(container);

})(jQuery);<|endoftext|>


<commit_before>import View from 'core/view/view';
import Command from 'core/infrastructure/command';
import Shortcut from 'core/infrastructure/shortcut';
import Navigation from 'core/navigation/navigation';
import {GRID_PREFIX} from 'core/definition';

export default class NavigationView extends View {
	constructor(model, markup, apply) {
		super(model);
		this.markup = markup;
		this.model = model;
		this.document = this.markup.document;
		const shortcut = new Shortcut(this.document, apply);
		const navigation = new Navigation(model);
		shortcut.register('navigation', navigation.commands);

		this.blur = new Command({
			execute: (row, column) => {
				this.rows[row].cells[column].classList.remove(`${GRID_PREFIX}-focus`);
			},
			canExecute: (row, column) => {
				return this.rows.length > row
					&& this.rows[row].cells.length > column;
			}
		});
		this.focus = new Command({
			execute: (row, column) => {
				const cell = this.rows[row].cells[column];
				cell.classList.add(`${GRID_PREFIX}-focus`);
			},
			canExecute: (row, column) => {
				return this.rows.length > row
					&& this.rows[row].cells.length > column;
			}
		});
		this.scrollTo = new Command({
			execute: (cell) => {
				this.scroll(markup.body, cell);
			}
		});

		model.navigationChanged.watch(e => {
			const nav = model.navigation();
			const newRow = nav.row == -1 ? 0 : nav.row;
			const newColumn = nav.column == -1 ? 0 : nav.column;
			const oldRow = e && e.changes.hasOwnProperty('row') ? e.changes.row.oldValue : newRow;
			const oldColumn = e && e.changes.hasOwnProperty('column') ? e.changes.column.oldValue : newColumn;

			if (this.blur.canExecute(newRow, newColumn) && oldRow > -1 && oldColumn > -1) {
				this.blur.execute(oldRow, oldColumn);
			}
			if (this.focus.canExecute(newRow, newColumn)) {
				this.focus.execute(newRow, newColumn);
				this.scrollTo.execute(this.rows[newRow].cells[newColumn]);
			}
		});

		model.viewChanged.watch(() => {
			model.navigation({
				column: -1,
				row: -1
			});
		});
	}

	get rows() {
		return this.markup.body.rows;
	}

	scroll(container, target) {
		const outer = container;
		target = target.getBoundingClientRect();
		container = container.getBoundingClientRect();

		if (container.left > target.left
			|| container.left > target.right
			|| container.right < target.left
			|| container.right < target.right) {
			if (container.left < target.left
				|| container.right < target.right) {
				outer.scrollLeft = target.width + this.model.layout().scroll.left;
			} else if (container.left > target.left
				|| container.left > target.right) {
				outer.scrollLeft = target.left - container.left + this.model.layout().scroll.left;
			}
		}
		if (container.top > target.top
			|| container.top > target.bottom
			|| container.bottom < target.top
			|| container.bottom < target.bottom) {
			if (container.top < target.top
				|| container.bottom < target.bottom) {
				outer.scrollTop = target.height + this.model.layout().scroll.top;
			} else if (container.top > target.top
				|| container.top > target.bottom) {
				outer.scrollTop = target.top - container.top + this.model.layout().scroll.top;
			}

		}
	}

}<commit_msg>fix scroll on page down / page up / end / home;<commit_after>import View from 'core/view/view';
import Command from 'core/infrastructure/command';
import Shortcut from 'core/infrastructure/shortcut';
import Navigation from 'core/navigation/navigation';
import {GRID_PREFIX} from 'core/definition';

export default class NavigationView extends View {
	constructor(model, markup, apply) {
		super(model);
		this.markup = markup;
		this.model = model;
		this.document = this.markup.document;
		const shortcut = new Shortcut(this.document, apply);
		const navigation = new Navigation(model);
		shortcut.register('navigation', navigation.commands);

		this.blur = new Command({
			execute: (row, column) => {
				this.rows[row].cells[column].classList.remove(`${GRID_PREFIX}-focus`);
			},
			canExecute: (row, column) => {
				return this.rows.length > row
					&& this.rows[row].cells.length > column;
			}
		});
		this.focus = new Command({
			execute: (row, column) => {
				const cell = this.rows[row].cells[column];
				cell.classList.add(`${GRID_PREFIX}-focus`);
			},
			canExecute: (row, column) => {
				return this.rows.length > row
					&& this.rows[row].cells.length > column;
			}
		});
		this.scrollTo = new Command({
			execute: (cell) => {
				this.scroll(markup.body, cell);
			}
		});

		model.navigationChanged.watch(e => {
			const nav = model.navigation();
			const newRow = nav.row == -1 ? 0 : nav.row;
			const newColumn = nav.column == -1 ? 0 : nav.column;
			const oldRow = e && e.changes.hasOwnProperty('row') ? e.changes.row.oldValue : newRow;
			const oldColumn = e && e.changes.hasOwnProperty('column') ? e.changes.column.oldValue : newColumn;

			if (this.blur.canExecute(newRow, newColumn) && oldRow > -1 && oldColumn > -1) {
				this.blur.execute(oldRow, oldColumn);
			}
			if (this.focus.canExecute(newRow, newColumn)) {
				this.focus.execute(newRow, newColumn);
				this.scrollTo.execute(this.rows[newRow].cells[newColumn]);
			}
		});

		model.viewChanged.watch(() => {
			model.navigation({
				column: -1,
				row: -1
			});
		});
	}

	get rows() {
		return this.markup.body.rows;
	}

	scroll(container, target) {
		const outer = container;
		target = target.getBoundingClientRect();
		container = container.getBoundingClientRect();

		if (container.left > target.left
			|| container.left > target.right
			|| container.right < target.left
			|| container.right < target.right) {
			if (container.left < target.left
				|| container.right < target.right) {
				outer.scrollLeft = target.right - container.right + this.model.layout().scroll.left;
			} else if (container.left > target.left
				|| container.left > target.right) {
				outer.scrollLeft = target.left - container.left + this.model.layout().scroll.left;
			}
		}
		if (container.top > target.top
			|| container.top > target.bottom
			|| container.bottom < target.top
			|| container.bottom < target.bottom) {
			if (container.top < target.top
				|| container.bottom < target.bottom) {
				outer.scrollTop = target.bottom - container.bottom + this.model.layout().scroll.top;
			} else if (container.top > target.top
				|| container.top > target.bottom) {
				outer.scrollTop = target.top - container.top + this.model.layout().scroll.top;
			}

		}
	}

}<|endoftext|>


<commit_before>// Image from http://i1.ytimg.com/vi/QH2-TGUlwu4/hqdefault.jpg
var GifEncoder = require('gif.js/src/GIFEncoder');

var Canvas = require('canvas');
var canvas = new Canvas(200, 200);

var ctx = canvas.getContext('2d');

ctx.font = '30px Impact';
ctx.fillText("Awesome!", 50, 100);

var te = ctx.measureText('Awesome!');
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.lineTo(50, 102);
ctx.lineTo(50 + te.width, 102);
ctx.stroke();

// console.log('');
// var fs = require('fs');
// fs.writeFileSync(__dirname + '/test.png', canvas.toDataURL().replace('data:image/png;base64,', ''), 'base64');

// Load in image-data.json
var imageData = require('./image-data');
var imgKeys = Object.getOwnPropertyNames(imageData).map(function (int) {
  return parseInt(int, 10);
});
var maxKey = imgKeys.reduce(function (a, b) {
  return Math.max(a, b);
}, 0);
var data = new Uint8ClampedArray(maxKey);
for (var i = 0; i < maxKey; i++) {
  data[i] = imageData[i];
}

global.atob = require('atob');
// Compare to canvas data
// https://gist.github.com/borismus/1032746
// var BASE64_MARKER = ';base64,';

// function convertDataURIToBinary(dataURI) {
//   var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
//   var base64 = dataURI.substring(base64Index);
//   // var raw = window.atob(base64);
//   var raw = atob(base64);
//   var rawLength = raw.length;
//   var array = new Uint8ClampedArray(new ArrayBuffer(rawLength));

//   for(i = 0; i < rawLength; i++) {
//     array[i] = raw.charCodeAt(i);
//   }
//   return array;
// }

// http://stackoverflow.com/questions/16194908/how-can-i-create-a-canvas-imagedata-array-from-an-arraybuffer-representation-of
// function convertDataURIToBinary(dataURI) {
//   // uri to Base64
//   b64 = dataURI.slice(dataURI.indexOf(',')+1);
//   // to String
//   str = atob(b64);
//   // to Array
//   arr = str.split('').map(function (e) {return e.charCodeAt(0);});
//   // to Uint8ClampedArray
//   u = new Uint8ClampedArray(arr); // [255, 56, 201, 8]

//   return u;
// }
// console.log(convertDataURIToBinary(canvas.toDataURL()));
// convertDataURIToBinary(canvas.toDataURL());

// TODO: Output canvas data to gif
// Output pre-built image data to gif
var gif = new GifEncoder(200, 200);

gif.addFrame(data);

// console.log(gif.out);

// finishRendering = function () {
//   var len = 0;
//   var imageParts = gif.frames;
//   for (var frame in imageParts) {
//     len += (frame.data.length - 1) * frame.pageSize + frame.cursor;
//   }
//   // TODO: Should we be hiding this line?
//   // len += frame.pageSize - frame.cursor;
//   // console.log "rendering finished - filesize #{ Math.round(len / 1000) }kb"
//   var data = new Uint8Array(len);
//   var offset = 0;
//   for (frame in imageParts) {
//     for (var page in frame.data) {
//       data.set(page, offset);
//       // TODO: This line is why we hid that line
//       if (i === frame.data.length - 1) {
//         offset += frame.cursor;
//       } else {
//         offset += frame.pageSize;
//       }
//     }
//   }

//   return data;
// };

// console.log(finishRendering());

console.log(gif.out);<commit_msg>Slowly unraveling muddled code<commit_after>// Image from http://i1.ytimg.com/vi/QH2-TGUlwu4/hqdefault.jpg
var GifEncoder = require('gif.js/src/GIFEncoder');

var Canvas = require('canvas');
var canvas = new Canvas(200, 200);

var ctx = canvas.getContext('2d');

ctx.font = '30px Impact';
ctx.fillText("Awesome!", 50, 100);

var te = ctx.measureText('Awesome!');
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.lineTo(50, 102);
ctx.lineTo(50 + te.width, 102);
ctx.stroke();

// console.log('');
// var fs = require('fs');
// fs.writeFileSync(__dirname + '/test.png', canvas.toDataURL().replace('data:image/png;base64,', ''), 'base64');

// Load in image-data.json
var imageData = require('./image-data');
var imgKeys = Object.getOwnPropertyNames(imageData).map(function (int) {
  return parseInt(int, 10);
});
var maxKey = imgKeys.reduce(function (a, b) {
  return Math.max(a, b);
}, 0);
var data = new Uint8ClampedArray(maxKey);
for (var i = 0; i < maxKey; i++) {
  data[i] = imageData[i];
}

global.atob = require('atob');
// Compare to canvas data
// https://gist.github.com/borismus/1032746
// var BASE64_MARKER = ';base64,';

// function convertDataURIToBinary(dataURI) {
//   var base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
//   var base64 = dataURI.substring(base64Index);
//   // var raw = window.atob(base64);
//   var raw = atob(base64);
//   var rawLength = raw.length;
//   var array = new Uint8ClampedArray(new ArrayBuffer(rawLength));

//   for(i = 0; i < rawLength; i++) {
//     array[i] = raw.charCodeAt(i);
//   }
//   return array;
// }

// http://stackoverflow.com/questions/16194908/how-can-i-create-a-canvas-imagedata-array-from-an-arraybuffer-representation-of
// function convertDataURIToBinary(dataURI) {
//   // uri to Base64
//   b64 = dataURI.slice(dataURI.indexOf(',')+1);
//   // to String
//   str = atob(b64);
//   // to Array
//   arr = str.split('').map(function (e) {return e.charCodeAt(0);});
//   // to Uint8ClampedArray
//   u = new Uint8ClampedArray(arr); // [255, 56, 201, 8]

//   return u;
// }
// console.log(convertDataURIToBinary(canvas.toDataURL()));
// convertDataURIToBinary(canvas.toDataURL());

// TODO: Output canvas data to gif
// Output pre-built image data to gif
var gif = new GifEncoder(200, 200);

gif.addFrame(data);

// console.log(gif.out);

finishRendering = function () {
  var len = 0;
  var imageParts = [gif];
  var frame;
  for (var i = 0; i < imageParts.length; i++) {
    frame = imageParts[i].out;
    len += (frame.pages.length - 1) * frame.constructor.pageSize + frame.cursor;
  }

  len += frame.constructor.pageSize - frame.cursor;

  // console.log "rendering finished - filesize #{ Math.round(len / 1000) }kb"
  var data = new Uint8Array(len);
  var offset = 0;
  for (frame in imageParts) {
    for (var page in frame.pages) {
      data.set(page, offset);
      if (i === frame.pages.length - 1) {
        offset += frame.cursor;
      } else {
        offset += frame.constructor.pageSize;
      }
    }
  }

  return data;
};

// // console.log(finishRendering());
console.log(finishRendering());

// console.log(gif.out);<|endoftext|>


<commit_before>const CSV = require('csv-string'); // open source csv parser and stringifier
const dwv = require('dwv'); // open source DICOM parser, viewer, and writer
const fs = require('fs'); // built-in node.js file system library
const fsPath = require('fs-path'); // open source high level fs function
const glob = require('glob'); // open source glob capability for node
const mv = require('mv'); // open source mv capability for node
const open = require('open'); // open source web browser URL opener
const path = require('path'); // built-in node.js path library
const process = require('process'); // built-in node.js process library
const spawn = require('child_process').spawn;

function automaDICOM() {
	this.in = null;
	this.rules = __dirname + path.sep + 'usr' + path.sep + 'rules.csv';
	this.out = null;
	this.append = __dirname + path.sep + 'usr' + path.sep + 'append.csv';
	this.server = false;
	this.verbose = true;
	this.list = false;
	this.specialFix = true;
	this.tags = [];
	this.values = [];
	this.newPaths = [];

	this.error = (err) => {
		console.log('ERROR: ' + err);
		process.exit(1);
	}

	this.getSubLevelTag = (tagReq, elements) => {
		switch (tagReq) {
			case 'CodeMeaning':
				return elements.getFromName('ViewCodeSequence')['x00080104'].value[0];
			case 'FrameLaterality':
				return elements.getFromName('SharedFunctionalGroupsSequence')['x00209071'].value[0]['x00209072'].value[0];
			default:
		}
	}

	this.fulfillTagReqs = (str, elements, values) => {
		let match, tagReq, tag;
		let useOgVal = false;
		let editIdx = -1;
		let regex = /\$[\w|\-|_]*/i;
		// loops while a match is found
		while (match = regex.exec(str)) {
			useOgVal = (match[0][1] == '$');
			tagReq = match[0].slice(((useOgVal) ? 2 : 1), match[0].length);
			// if values is not defined or if the requested tag is not found in this.tags
			if (useOgVal || (editIdx = this.tags.indexOf(tagReq)) <= -1) {
				// get the tag from the zero level DICOM by name
				tag = elements.getFromName(tagReq);
				if (tag == null) {
					tag = this.getSubLevelTag(tagReq, elements);
				}
				// if it's still null it wasn't found
				if (tag == null) {
					this.error(tagReq + ' tag not found!!');
				}
			} else {
				// get the tag from the values array
				tag = values[editIdx];
			}
			// replace the request with the tag itself, the quotes are necessary
			str = str.replace('$' + tagReq, `\'${tag}\'`);
		}
		// note that eval is used to evaluate user javascript dynamically!
		// direct access to String methods gives users advanced control
		// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
		try {
			str = eval(str);
		} catch (err) {}
		return str;
	}

	this.errorCheck = (tag, value) => {
		switch (tag) {
			case 'AcquisitionDate':
			case 'ContentDate':
			case 'InstanceCreationDate':
			case 'PatientBirthDate':
			case 'StudyDate':
				if (typeof value !== 'string') {
					this.error(`${tag} ${value}
Dates must be entered as a String in a standard format, ex:'YYYYMMDD'`);
				}
				let isYear = (parseInt(value.slice(0, 4)) >= 1900);
				let isMonth = (parseInt(value.slice(4, 6)) <= 12);
				let isDay = (parseInt(value.slice(6, 8)) <= 31);
				if ((!isYear || !isMonth || !isDay)) {
					console.log(`${tag} ${value}
Dates must be entered as a String in a standard format, ex:'YYYYMMDD'`);
				}
				break;
			case 'AcquisitionTime':
			case 'ContentTime':
			case 'InstanceCreationTime':
			case 'PatientBirthTime':
			case 'StudyTime':
				if (typeof value !== 'string') {
					this.error(`${tag} ${value}
Times must be entered as a String in a standard format, ex:'HHMMSS'`);
				}
				break;
			default:
		}
	}

	this.edit = (file, files) => {
		let i = 0;
		// loads the parser and writer from the open source library DWV
		let parser = new dwv.dicom.DicomParser();
		let writer = new dwv.dicom.DicomWriter();
		if (this.verbose) {
			console.log('loading: ' + file);
		}
		try {
			// parse the array buffer of the file
			parser.parse(new Uint8Array(fs.readFileSync(file)).buffer);
		} catch (err) {
			fs.renameSync(file, file.slice(0, file.length - 4));
			this.error(`failed to load ${file.slice(0, file.length-4)}
This file does not have an extension and is not a DICOM image.
Please give this file a proper extension or remove it from the input directory.`);
			return;
		}
		// get the tags
		let elements = parser.getDicomElements();
		if (this.list) {
			console.log(elements.dump());
		}
		// the default rule must be included, it simply copies all other tag's value(s)
		// hard coding this inside this program pervents users from screwing it up
		// also for user convience the action of their rules is always 'replace'
		let rules = {
			default: {
				action: 'copy',
				value: null
			}
		};
		// a seperate copy of the values array must be used for each image
		let values = this.values.slice();
		// rule objects conforms to the format the DICOM writer expects
		this.tags.forEach((tag, i) => {
			values[i] = this.fulfillTagReqs(values[i], elements, values);
			this.errorCheck(tag, values[i]);
			rules[tag] = {
				action: 'replace',
				value: values[i]
			};
		});
		if (this.verbose) {
			values.forEach((value, i) => {
				console.log(`${this.tags[i]} = ${value}`);
			});
		}
		// the rules are applied immediately after they are set
		writer.rules = rules;
		// buffer gets the modified DICOM file
		let buffer = writer.getBuffer(parser.getRawDicomElements());
		// let's decide where to write the file!
		let dir, imgName;
		let newPath = '';
		if (this.out != null) {
			dir = this.out;
			for (i = 0; i < this.append.length - 1; i++) {
				dir += path.sep + this.fulfillTagReqs(this.append[i][0], elements, values);
			}
			imgName = this.fulfillTagReqs(this.append[this.append.length - 1][0], elements, values);
			// at the end of this loop it is assured that a unique file name has been created
			for (i = 0; newPath == '' || this.newPaths.includes(newPath) || fs.existsSync(newPath); i++) {
				newPath = dir + path.sep + `${imgName}_${i.toString()}.dcm`;
				if (newPath == file) {
					break;
				}
			}
		} else {
			// get the directory of the file
			dir = path.parse(file).dir;
			// prepend anon_ to the file name
			imgName = 'anon_' + path.parse(file).name + '.dcm';
			newPath = dir + path.sep + imgName;
		}
		this.newPaths.push(newPath);
		if (this.verbose) {
			console.log('wrote: ' + newPath + '\n');
		}

		// writeFile can be used asynchronously here, this is advantageous
		// fsPath makes any new directories if necessary
		fsPath.writeFile(newPath, Buffer(new Uint8Array(buffer)), (err) => {
			if (err) throw err;
			if (this.server) {
				// if running in server mode (continuous operation) delete the original file
				fs.unlink(file);
			}
			if (this.specialFix) {
				let dcmodify = spawn(__dirname + path.sep + 'dcmodify', [newPath, '-i', 'ImageLaterality=' + this.fulfillTagRequest("$FrameLaterality.slice(0,1) + ' ' + (($CodeMeaning == 'cranio-caudal ')?'CC':'MLO')")]);

				dcmodify.stdout.on('data', (data) => {
					console.log(`stdout: ${data}`);
				});

				dcmodify.stderr.on('data', (data) => {
					console.log(`stderr: ${data}`);
				});

				dcmodify.on('close', (code) => {
					console.log(`child process exited with code ${code}`);
					fs.unlink(newPath + '.bak');
				});
			}
		});
	};

	this.setup = (err, files) => {
		if (err || typeof files == 'undefined' || files.length == 0) {
			console.log('ERROR: invalid path, no files found');
			this.error(err);
		}
		// read the rules file synchronously
		this.rules = fs.readFileSync(this.rules, 'utf8');
		// parse the csv file into arrays we can use
		this.rules = CSV.parse(this.rules, ';');
		this.rules.forEach((rule) => {
			this.tags.push(rule[0]);
			this.values.push(rule[1]);
		});
		if (this.out != null) {
			this.append = fs.readFileSync(this.append, 'utf8');
			this.append = CSV.parse(this.append);
		}

		for (let i = 0; i < files.length; i++) {
			if (!fs.statSync(files[i]).isDirectory()) {
				if (path.parse(files[i]).ext == '') {
					fs.renameSync(files[i], files[i] += '.dcm');
				}
				if (path.parse(files[i]).ext == '.dcm') {
					this.edit(files[i], files);
				}
			}
		}
	}

	this.run = () => {
		if (this.in != null) {
			console.log('');
			console.log('input: ' + this.in + '\n');
			// if the input path is a directory send it straight to the setup function
			// else glob for leaves of the fs
			if (!fs.statSync(this.in).isDirectory()) {
				this.setup(null, [this.in]);
			} else {
				glob(this.in + path.sep + '**' + path.sep + '*', this.setup);
			}
		} else {
			this.error('required input path not specified');
		}
	}
}

module.exports = new automaDICOM();
<commit_msg>v0.1.6.1<commit_after>const CSV = require('csv-string'); // open source csv parser and stringifier
const dwv = require('dwv'); // open source DICOM parser, viewer, and writer
const fs = require('fs'); // built-in node.js file system library
const fsPath = require('fs-path'); // open source high level fs function
const glob = require('glob'); // open source glob capability for node
const mv = require('mv'); // open source mv capability for node
const open = require('open'); // open source web browser URL opener
const path = require('path'); // built-in node.js path library
const process = require('process'); // built-in node.js process library
const spawn = require('child_process').spawn;

function automaDICOM() {
	this.in = null;
	this.rules = __dirname + path.sep + 'usr' + path.sep + 'rules.csv';
	this.out = null;
	this.append = __dirname + path.sep + 'usr' + path.sep + 'append.csv';
	this.server = false;
	this.verbose = true;
	this.list = false;
	this.specialFix = true;
	this.tags = [];
	this.values = [];
	this.newPaths = [];

	this.error = (err) => {
		console.log('ERROR: ' + err);
		process.exit(1);
	}

	this.getSubLevelTag = (tagReq, elements) => {
		switch (tagReq) {
			case 'CodeMeaning':
				return elements.getFromName('ViewCodeSequence')['x00080104'].value[0];
			case 'FrameLaterality':
				return elements.getFromName('SharedFunctionalGroupsSequence')['x00209071'].value[0]['x00209072'].value[0];
			default:
		}
	}

	this.fulfillTagReqs = (str, elements, values) => {
		let match, tagReq, tag;
		let useOgVal = false;
		let editIdx = -1;
		let regex = /\$[\w|\-|_]*/i;
		// loops while a match is found
		while (match = regex.exec(str)) {
			useOgVal = (match[0][1] == '$');
			tagReq = match[0].slice(((useOgVal) ? 2 : 1), match[0].length);
			// if values is not defined or if the requested tag is not found in this.tags
			if (useOgVal || (editIdx = this.tags.indexOf(tagReq)) <= -1) {
				// get the tag from the zero level DICOM by name
				tag = elements.getFromName(tagReq);
				if (tag == null) {
					tag = this.getSubLevelTag(tagReq, elements);
				}
				// if it's still null it wasn't found
				if (tag == null) {
					this.error(tagReq + ' tag not found!!');
				}
			} else {
				// get the tag from the values array
				tag = values[editIdx];
			}
			// replace the request with the tag itself, the quotes are necessary
			str = str.replace('$' + tagReq, `\'${tag}\'`);
		}
		// note that eval is used to evaluate user javascript dynamically!
		// direct access to String methods gives users advanced control
		// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
		try {
			str = eval(str);
		} catch (err) {}
		return str;
	}

	this.errorCheck = (tag, value) => {
		switch (tag) {
			case 'AcquisitionDate':
			case 'ContentDate':
			case 'InstanceCreationDate':
			case 'PatientBirthDate':
			case 'StudyDate':
				if (typeof value !== 'string') {
					this.error(`${tag} ${value}
Dates must be entered as a String in a standard format, ex:'YYYYMMDD'`);
				}
				let isYear = (parseInt(value.slice(0, 4)) >= 1900);
				let isMonth = (parseInt(value.slice(4, 6)) <= 12);
				let isDay = (parseInt(value.slice(6, 8)) <= 31);
				if ((!isYear || !isMonth || !isDay)) {
					console.log(`${tag} ${value}
Dates must be entered as a String in a standard format, ex:'YYYYMMDD'`);
				}
				break;
			case 'AcquisitionTime':
			case 'ContentTime':
			case 'InstanceCreationTime':
			case 'PatientBirthTime':
			case 'StudyTime':
				if (typeof value !== 'string') {
					this.error(`${tag} ${value}
Times must be entered as a String in a standard format, ex:'HHMMSS'`);
				}
				break;
			default:
		}
	}

	this.edit = (file, files) => {
		let i = 0;
		// loads the parser and writer from the open source library DWV
		let parser = new dwv.dicom.DicomParser();
		let writer = new dwv.dicom.DicomWriter();
		if (this.verbose) {
			console.log('loading: ' + file);
		}
		try {
			// parse the array buffer of the file
			parser.parse(new Uint8Array(fs.readFileSync(file)).buffer);
		} catch (err) {
			fs.renameSync(file, file.slice(0, file.length - 4));
			this.error(`failed to load ${file.slice(0, file.length-4)}
This file does not have an extension and is not a DICOM image.
Please give this file a proper extension or remove it from the input directory.`);
			return;
		}
		// get the tags
		let elements = parser.getDicomElements();
		if (this.list) {
			console.log(elements.dump());
		}
		// the default rule must be included, it simply copies all other tag's value(s)
		// hard coding this inside this program pervents users from screwing it up
		// also for user convience the action of their rules is always 'replace'
		let rules = {
			default: {
				action: 'copy',
				value: null
			}
		};
		// a seperate copy of the values array must be used for each image
		let values = this.values.slice();
		// rule objects conforms to the format the DICOM writer expects
		this.tags.forEach((tag, i) => {
			values[i] = this.fulfillTagReqs(values[i], elements, values);
			this.errorCheck(tag, values[i]);
			rules[tag] = {
				action: 'replace',
				value: values[i]
			};
		});
		if (this.verbose) {
			values.forEach((value, i) => {
				console.log(`${this.tags[i]} = ${value}`);
			});
		}
		// the rules are applied immediately after they are set
		writer.rules = rules;
		// buffer gets the modified DICOM file
		let buffer = writer.getBuffer(parser.getRawDicomElements());
		// let's decide where to write the file!
		let dir, imgName;
		let newPath = '';
		if (this.out != null) {
			dir = this.out;
			for (i = 0; i < this.append.length - 1; i++) {
				dir += path.sep + this.fulfillTagReqs(this.append[i][0], elements, values);
			}
			imgName = this.fulfillTagReqs(this.append[this.append.length - 1][0], elements, values);
			// at the end of this loop it is assured that a unique file name has been created
			for (i = 0; newPath == '' || this.newPaths.includes(newPath) || fs.existsSync(newPath); i++) {
				newPath = dir + path.sep + `${imgName}_${i.toString()}.dcm`;
				if (newPath == file) {
					break;
				}
			}
		} else {
			// get the directory of the file
			dir = path.parse(file).dir;
			// prepend anon_ to the file name
			imgName = 'anon_' + path.parse(file).name + '.dcm';
			newPath = dir + path.sep + imgName;
		}
		this.newPaths.push(newPath);
		if (this.verbose) {
			console.log('wrote: ' + newPath + '\n');
		}

		// writeFile can be used asynchronously here, this is advantageous
		// fsPath makes any new directories if necessary
		fsPath.writeFile(newPath, Buffer(new Uint8Array(buffer)), (err) => {
			if (err) throw err;
			if (this.server) {
				// if running in server mode (continuous operation) delete the original file
				fs.unlink(file);
			}
			if (this.specialFix) {
				let dcmodify = spawn(__dirname + path.sep + 'dcmodify', [newPath, '-i', 'ImageLaterality=' + this.fulfillTagReqs("$FrameLaterality.slice(0,1) + ' ' + (($CodeMeaning == 'cranio-caudal ')?'CC':'MLO')", elements, values)]);

				dcmodify.stdout.on('data', (data) => {
					console.log(`stdout: ${data}`);
				});

				dcmodify.stderr.on('data', (data) => {
					console.log(`stderr: ${data}`);
				});

				dcmodify.on('close', (code) => {
					console.log(`child process exited with code ${code}`);
					fs.unlink(newPath + '.bak');
				});
			}
		});
	};

	this.setup = (err, files) => {
		if (err || typeof files == 'undefined' || files.length == 0) {
			console.log('ERROR: invalid path, no files found');
			this.error(err);
		}
		// read the rules file synchronously
		this.rules = fs.readFileSync(this.rules, 'utf8');
		// parse the csv file into arrays we can use
		this.rules = CSV.parse(this.rules, ';');
		this.rules.forEach((rule) => {
			this.tags.push(rule[0]);
			this.values.push(rule[1]);
		});
		if (this.out != null) {
			this.append = fs.readFileSync(this.append, 'utf8');
			this.append = CSV.parse(this.append);
		}

		for (let i = 0; i < files.length; i++) {
			if (!fs.statSync(files[i]).isDirectory()) {
				if (path.parse(files[i]).ext == '') {
					fs.renameSync(files[i], files[i] += '.dcm');
				}
				if (path.parse(files[i]).ext == '.dcm') {
					this.edit(files[i], files);
				}
			}
		}
	}

	this.run = () => {
		if (this.in != null) {
			console.log('');
			console.log('input: ' + this.in + '\n');
			// if the input path is a directory send it straight to the setup function
			// else glob for leaves of the fs
			if (!fs.statSync(this.in).isDirectory()) {
				this.setup(null, [this.in]);
			} else {
				glob(this.in + path.sep + '**' + path.sep + '*', this.setup);
			}
		} else {
			this.error('required input path not specified');
		}
	}
}

module.exports = new automaDICOM();
<|endoftext|>


<commit_before>/**
 * jspsych-webgazer-validate
 * Josh de Leeuw
 **/

 jsPsych.plugins["webgazer-validate"] = (function() {

  var plugin = {};

  plugin.info = {
    name: 'webgazer-validate',
    description: '',
    parameters: {
      validation_points: {
        type: jsPsych.plugins.parameterType.INT,
        default: [[10,10], [10,50], [10,90], [50,10], [50,50], [50,90], [90,10], [90,50], [90,90]]
      },
      validation_point_coordinates: {
        type: jsPsych.plugins.parameterType.STRING,
        default: 'percent' // options: 'percent', 'center-offset-pixels'
      },
      roi_radius: {
        type: jsPsych.plugins.parameterType.INT,
        default: 200
      },
      randomize_validation_order: {
        type: jsPsych.plugins.parameterType.BOOL,
        default: false
      },
      time_to_saccade: {
        type: jsPsych.plugins.parameterType.INT,
        default: 1000
      },
      validation_duration: {
        type: jsPsych.plugins.parameterType.INT,
        default: 2000
      },
      point_size:{
        type: jsPsych.plugins.parameterType.INT,
        default: 20
      },
      show_validation_data: {
        type: jsPsych.plugins.parameterType.BOOL,
        default: false
      }
    }
  }

  plugin.trial = function(display_element, trial) {

    var trial_data = {}
    trial_data.raw_gaze = [];
    trial_data.percent_in_roi = [];
    trial_data.average_offset = [];
    trial_data.validation_points = null;

    var html = `

`

    display_element.innerHTML = html;

    var wg_container = display_element.querySelector('#webgazer-validate-container');

    var points_completed = -1;
    var val_points = null;
    var start = performance.now();

    validate();

    function validate(){

      if(trial.randomize_validation_order){
        val_points = jsPsych.randomization.shuffle(trial.validation_points);
      } else {
        val_points = trial.validation_points;
      }
      trial_data.validation_points = val_points;
      points_completed = -1;
      //jsPsych.extensions['webgazer'].resume();
      jsPsych.extensions.webgazer.startSampleInterval();
      //jsPsych.extensions.webgazer.showPredictions();
      next_validation_point();
    }

    function next_validation_point(){
      points_completed++;
      if(points_completed == val_points.length){
        validation_done();
      } else {
        var pt = val_points[points_completed];
        validation_display(pt);
      }
    }

    function validation_display(pt){
      var pt_html = drawValidationPoint(pt[0], pt[1]);
      wg_container.innerHTML = pt_html;

      var pt_dom = wg_container.querySelector('.validation-point');

      var br = pt_dom.getBoundingClientRect();
      var x = br.left + br.width / 2;
      var y = br.top + br.height / 2;

      var pt_start_val = performance.now() + trial.time_to_saccade;
      var pt_finish = pt_start_val + trial.validation_duration;

      var pt_data = [];

      var cancelGazeUpdate = jsPsych.extensions['webgazer'].onGazeUpdate(function(prediction){
        if(performance.now() > pt_start_val){
          pt_data.push({x: prediction.x, y: prediction.y, dx: prediction.x - x, dy: prediction.y - y, t: Math.round(prediction.t-start)});
        }
      });

      requestAnimationFrame(function watch_dot(){
        if(performance.now() < pt_finish){
          requestAnimationFrame(watch_dot);
        } else {
          trial_data.raw_gaze.push(pt_data);
          cancelGazeUpdate();

          next_validation_point();
        }
      });

    }

    function drawValidationPoint(x,y){
      if(trial.validation_point_coordinates == 'percent'){
        return drawValidationPoint_PercentMode(x,y);
      }
      if(trial.validation_point_coordinates == 'center-offset-pixels'){
        return drawValidationPoint_CenterOffsetMode(x,y);
      }
    }

    function drawValidationPoint_PercentMode(x,y){
      return `

`
    }

    function drawValidationPoint_CenterOffsetMode(x,y){
      return `

`
    }

    function drawCircle(target_x, target_y, dx, dy, r){
      if(trial.validation_point_coordinates == 'percent'){
        return drawCircle_PercentMode(target_x, target_y, dx, dy, r);
      }
      if(trial.validation_point_coordinates == 'center-offset-pixels'){
        return drawCircle_CenterOffsetMode(target_x, target_y, dx, dy, r);
      }
    }

    function drawCircle_PercentMode(target_x, target_y, dx, dy, r){
      var html = `

      `
      return html;
    }

    function drawCircle_CenterOffsetMode(target_x, target_y, dx, dy, r){
      var html = `

      `
      return html;
    }

    function drawRawDataPoint(target_x, target_y, dx, dy, ){
      if(trial.validation_point_coordinates == 'percent'){
        return drawRawDataPoint_PercentMode(target_x, target_y, dx, dy);
      }
      if(trial.validation_point_coordinates == 'center-offset-pixels'){
        return drawRawDataPoint_CenterOffsetMode(target_x, target_y, dx, dy);
      }
    }

    function drawRawDataPoint_PercentMode(target_x, target_y, dx, dy){
      var color = Math.sqrt(dx*dx + dy*dy) <= trial.roi_radius ? '#afa' : '#faa';
      return `

`
    }

    function drawRawDataPoint_CenterOffsetMode(target_x, target_y, dx, dy){
      var color = Math.sqrt(dx*dx + dy*dy) <= trial.roi_radius ? '#afa' : '#faa';
      return `

`
    }

    function median(arr){
      var mid = Math.floor(arr.length/2);
      var sorted_arr = arr.sort((a,b) => a-b);
      if(arr.length % 2 == 0){
        return sorted_arr[mid-1] + sorted_arr[mid] / 2;
      } else {
        return sorted_arr[mid];
      }
    }

    function calculateGazeCentroid(gazeData){

      var x_diff_m = gazeData.reduce(function(accumulator, currentValue, index){
        accumulator += currentValue.dx;
        if(index == gazeData.length-1){
          return accumulator / gazeData.length;
        } else {
          return accumulator;
        }
      }, 0);

      var y_diff_m = gazeData.reduce(function(accumulator, currentValue, index){
        accumulator += currentValue.dy;
        if(index == gazeData.length-1){
          return accumulator / gazeData.length;
        } else {
          return accumulator;
        }
      }, 0);

      var median_distance = median(gazeData.map(function(x){ return(Math.sqrt(Math.pow(x.dx-x_diff_m,2) + Math.pow(x.dy-y_diff_m,2)))}));

      return {
        x: x_diff_m,
        y: y_diff_m,
        r: median_distance
      }
    }

    function calculatePercentInROI(gazeData){
      var distances = gazeData.map(function(p){
        return(Math.sqrt(Math.pow(p.dx,2) + Math.pow(p.dy,2)))
      });
      var sum_in_roi = distances.reduce(function(accumulator, currentValue){
        if(currentValue <= trial.roi_radius){
          accumulator++;
        }
        return accumulator;
      }, 0);
      var percent = sum_in_roi / gazeData.length * 100;
      return percent;
    }

    function calculateSampleRate(gazeData){
      var mean_diff = [];
      for(var i=0; i<gazeData.length; i++){
        if(gazeData[i].length > 1){
          var t_diff = [];
          for(var j=1; j<gazeData[i].length; j++){
            t_diff.push(gazeData[i][j].t - gazeData[i][j-1].t)
          }
          mean_diff.push(t_diff.reduce(function(a,b) { return(a+b) },0) / t_diff.length);
        }
      }
      if(mean_diff.length > 0){
        return 1000 / (mean_diff.reduce(function(a,b) { return(a+b) }, 0) / mean_diff.length);
      } else {
        return null;
      }

    }

    function validation_done(){
      trial_data.samples_per_sec = calculateSampleRate(trial_data.raw_gaze).toFixed(2);
      for(var i=0; i<trial.validation_points.length; i++){
        trial_data.percent_in_roi[i] = calculatePercentInROI(trial_data.raw_gaze[i]);
        trial_data.average_offset[i] = calculateGazeCentroid(trial_data.raw_gaze[i]);
      }
      if(trial.show_validation_data){

        show_validation_data();
      } else {
        end_trial();
      }
    }

    function show_validation_data(){
      var html = '';
      for(var i=0; i<trial.validation_points.length; i++){
        html += drawValidationPoint(trial.validation_points[i][0], trial.validation_points[i][1]);
        html += drawCircle(trial.validation_points[i][0], trial.validation_points[i][1], 0, 0, trial.roi_radius);
        for(var j=0; j<trial_data.raw_gaze[i].length; j++){
          html += drawRawDataPoint(trial.validation_points[i][0], trial.validation_points[i][1], trial_data.raw_gaze[i][j].dx, trial_data.raw_gaze[i][j].dy)
        }
      }

      html += '<button id="cont" style="position:absolute; top: 50%; left:calc(50% - 50px); width: 100px;" class="jspsych-btn">Continue</btn>';
      wg_container.innerHTML = html;
      wg_container.querySelector('#cont').addEventListener('click', function(){
        jsPsych.extensions.webgazer.pause();
        end_trial();
      });
      // turn on webgazer's loop
      jsPsych.extensions.webgazer.showPredictions();
      jsPsych.extensions.webgazer.stopSampleInterval();
      jsPsych.extensions.webgazer.resume();
    }

    // function to end trial when it is time
    function end_trial() {
      jsPsych.extensions.webgazer.stopSampleInterval();

      // kill any remaining setTimeout handlers
      jsPsych.pluginAPI.clearAllTimeouts();

      // clear the display
      display_element.innerHTML = '';

      // move on to the next trial
      jsPsych.finishTrial(trial_data);
    };

  };

  return plugin;
})();<commit_msg>return 0 when no gazeData (possibly fixes #1724)<commit_after>/**
 * jspsych-webgazer-validate
 * Josh de Leeuw
 **/

 jsPsych.plugins["webgazer-validate"] = (function() {

  var plugin = {};

  plugin.info = {
    name: 'webgazer-validate',
    description: '',
    parameters: {
      validation_points: {
        type: jsPsych.plugins.parameterType.INT,
        default: [[10,10], [10,50], [10,90], [50,10], [50,50], [50,90], [90,10], [90,50], [90,90]]
      },
      validation_point_coordinates: {
        type: jsPsych.plugins.parameterType.STRING,
        default: 'percent' // options: 'percent', 'center-offset-pixels'
      },
      roi_radius: {
        type: jsPsych.plugins.parameterType.INT,
        default: 200
      },
      randomize_validation_order: {
        type: jsPsych.plugins.parameterType.BOOL,
        default: false
      },
      time_to_saccade: {
        type: jsPsych.plugins.parameterType.INT,
        default: 1000
      },
      validation_duration: {
        type: jsPsych.plugins.parameterType.INT,
        default: 2000
      },
      point_size:{
        type: jsPsych.plugins.parameterType.INT,
        default: 20
      },
      show_validation_data: {
        type: jsPsych.plugins.parameterType.BOOL,
        default: false
      }
    }
  }

  plugin.trial = function(display_element, trial) {

    var trial_data = {}
    trial_data.raw_gaze = [];
    trial_data.percent_in_roi = [];
    trial_data.average_offset = [];
    trial_data.validation_points = null;

    var html = `

`

    display_element.innerHTML = html;

    var wg_container = display_element.querySelector('#webgazer-validate-container');

    var points_completed = -1;
    var val_points = null;
    var start = performance.now();

    validate();

    function validate(){

      if(trial.randomize_validation_order){
        val_points = jsPsych.randomization.shuffle(trial.validation_points);
      } else {
        val_points = trial.validation_points;
      }
      trial_data.validation_points = val_points;
      points_completed = -1;
      //jsPsych.extensions['webgazer'].resume();
      jsPsych.extensions.webgazer.startSampleInterval();
      //jsPsych.extensions.webgazer.showPredictions();
      next_validation_point();
    }

    function next_validation_point(){
      points_completed++;
      if(points_completed == val_points.length){
        validation_done();
      } else {
        var pt = val_points[points_completed];
        validation_display(pt);
      }
    }

    function validation_display(pt){
      var pt_html = drawValidationPoint(pt[0], pt[1]);
      wg_container.innerHTML = pt_html;

      var pt_dom = wg_container.querySelector('.validation-point');

      var br = pt_dom.getBoundingClientRect();
      var x = br.left + br.width / 2;
      var y = br.top + br.height / 2;

      var pt_start_val = performance.now() + trial.time_to_saccade;
      var pt_finish = pt_start_val + trial.validation_duration;

      var pt_data = [];

      var cancelGazeUpdate = jsPsych.extensions['webgazer'].onGazeUpdate(function(prediction){
        if(performance.now() > pt_start_val){
          pt_data.push({x: prediction.x, y: prediction.y, dx: prediction.x - x, dy: prediction.y - y, t: Math.round(prediction.t-start)});
        }
      });

      requestAnimationFrame(function watch_dot(){
        if(performance.now() < pt_finish){
          requestAnimationFrame(watch_dot);
        } else {
          trial_data.raw_gaze.push(pt_data);
          cancelGazeUpdate();

          next_validation_point();
        }
      });

    }

    function drawValidationPoint(x,y){
      if(trial.validation_point_coordinates == 'percent'){
        return drawValidationPoint_PercentMode(x,y);
      }
      if(trial.validation_point_coordinates == 'center-offset-pixels'){
        return drawValidationPoint_CenterOffsetMode(x,y);
      }
    }

    function drawValidationPoint_PercentMode(x,y){
      return `

`
    }

    function drawValidationPoint_CenterOffsetMode(x,y){
      return `

`
    }

    function drawCircle(target_x, target_y, dx, dy, r){
      if(trial.validation_point_coordinates == 'percent'){
        return drawCircle_PercentMode(target_x, target_y, dx, dy, r);
      }
      if(trial.validation_point_coordinates == 'center-offset-pixels'){
        return drawCircle_CenterOffsetMode(target_x, target_y, dx, dy, r);
      }
    }

    function drawCircle_PercentMode(target_x, target_y, dx, dy, r){
      var html = `

      `
      return html;
    }

    function drawCircle_CenterOffsetMode(target_x, target_y, dx, dy, r){
      var html = `

      `
      return html;
    }

    function drawRawDataPoint(target_x, target_y, dx, dy, ){
      if(trial.validation_point_coordinates == 'percent'){
        return drawRawDataPoint_PercentMode(target_x, target_y, dx, dy);
      }
      if(trial.validation_point_coordinates == 'center-offset-pixels'){
        return drawRawDataPoint_CenterOffsetMode(target_x, target_y, dx, dy);
      }
    }

    function drawRawDataPoint_PercentMode(target_x, target_y, dx, dy){
      var color = Math.sqrt(dx*dx + dy*dy) <= trial.roi_radius ? '#afa' : '#faa';
      return `

`
    }

    function drawRawDataPoint_CenterOffsetMode(target_x, target_y, dx, dy){
      var color = Math.sqrt(dx*dx + dy*dy) <= trial.roi_radius ? '#afa' : '#faa';
      return `

`
    }

    function median(arr){
      var mid = Math.floor(arr.length/2);
      var sorted_arr = arr.sort((a,b) => a-b);
      if(arr.length % 2 == 0){
        return sorted_arr[mid-1] + sorted_arr[mid] / 2;
      } else {
        return sorted_arr[mid];
      }
    }

    function calculateGazeCentroid(gazeData){

      var x_diff_m = gazeData.reduce(function(accumulator, currentValue, index){
        accumulator += currentValue.dx;
        if(index == gazeData.length-1){
          return accumulator / gazeData.length;
        } else {
          return accumulator;
        }
      }, 0);

      var y_diff_m = gazeData.reduce(function(accumulator, currentValue, index){
        accumulator += currentValue.dy;
        if(index == gazeData.length-1){
          return accumulator / gazeData.length;
        } else {
          return accumulator;
        }
      }, 0);

      var median_distance = median(gazeData.map(function(x){ return(Math.sqrt(Math.pow(x.dx-x_diff_m,2) + Math.pow(x.dy-y_diff_m,2)))}));

      return {
        x: x_diff_m,
        y: y_diff_m,
        r: median_distance
      }
    }

    function calculatePercentInROI(gazeData){
      var distances = gazeData.map(function(p){
        return(Math.sqrt(Math.pow(p.dx,2) + Math.pow(p.dy,2)))
      });
      var sum_in_roi = distances.reduce(function(accumulator, currentValue){
        if(currentValue <= trial.roi_radius){
          accumulator++;
        }
        return accumulator;
      }, 0);
      var percent = sum_in_roi / gazeData.length * 100;
      return percent;
    }

    function calculateSampleRate(gazeData){
      var mean_diff = [];
      if(gazeData.length == 0){
        return 0;
      }
      for(var i=0; i<gazeData.length; i++){
        if(gazeData[i].length > 1){
          var t_diff = [];
          for(var j=1; j<gazeData[i].length; j++){
            t_diff.push(gazeData[i][j].t - gazeData[i][j-1].t)
          }
          mean_diff.push(t_diff.reduce(function(a,b) { return(a+b) },0) / t_diff.length);
        }
      }
      if(mean_diff.length > 0){
        return 1000 / (mean_diff.reduce(function(a,b) { return(a+b) }, 0) / mean_diff.length);
      } else {
        return null;
      }
    }

    function validation_done(){
      trial_data.samples_per_sec = calculateSampleRate(trial_data.raw_gaze).toFixed(2);
      for(var i=0; i<trial.validation_points.length; i++){
        trial_data.percent_in_roi[i] = calculatePercentInROI(trial_data.raw_gaze[i]);
        trial_data.average_offset[i] = calculateGazeCentroid(trial_data.raw_gaze[i]);
      }
      if(trial.show_validation_data){

        show_validation_data();
      } else {
        end_trial();
      }
    }

    function show_validation_data(){
      var html = '';
      for(var i=0; i<trial.validation_points.length; i++){
        html += drawValidationPoint(trial.validation_points[i][0], trial.validation_points[i][1]);
        html += drawCircle(trial.validation_points[i][0], trial.validation_points[i][1], 0, 0, trial.roi_radius);
        for(var j=0; j<trial_data.raw_gaze[i].length; j++){
          html += drawRawDataPoint(trial.validation_points[i][0], trial.validation_points[i][1], trial_data.raw_gaze[i][j].dx, trial_data.raw_gaze[i][j].dy)
        }
      }

      html += '<button id="cont" style="position:absolute; top: 50%; left:calc(50% - 50px); width: 100px;" class="jspsych-btn">Continue</btn>';
      wg_container.innerHTML = html;
      wg_container.querySelector('#cont').addEventListener('click', function(){
        jsPsych.extensions.webgazer.pause();
        end_trial();
      });
      // turn on webgazer's loop
      jsPsych.extensions.webgazer.showPredictions();
      jsPsych.extensions.webgazer.stopSampleInterval();
      jsPsych.extensions.webgazer.resume();
    }

    // function to end trial when it is time
    function end_trial() {
      jsPsych.extensions.webgazer.stopSampleInterval();

      // kill any remaining setTimeout handlers
      jsPsych.pluginAPI.clearAllTimeouts();

      // clear the display
      display_element.innerHTML = '';

      // move on to the next trial
      jsPsych.finishTrial(trial_data);
    };

  };

  return plugin;
})();<|endoftext|>