<commit_before>var _ = require('underscore'),
	csv = require('csv'),
	fs = require('fs'),
	restify = require('restify'),

	BOROUGHS_NAMES = [ "Barking and Dagenham", "Barnet", "Bexley", "Brent",
		"Bromley", "Camden", "City of London", "Croydon", "Ealing", "Enfield",
		"Greenwich", "Hackney", "Hammersmith and Fulham", "Haringey", "Harrow",
		"Havering", "Hillingdon", "Hounslow", "Islington",
		"Kensington and Chelsea", "Kingston upon Thames", "Lambeth", "Lewisham",
		"Merton", "Newham", "Redbridge", "Richmond upon Thames", "Southwark",
		"Sutton", "Tower Hamlets", "Waltham Forest", "Wandsworth",
		"Westminster" ],

	// At the moment of writing, and according to the colour levels we are
	// currently using for the legend, of the statons facing closure Southwark is the
	// only single station closure that produces a visible effect on the map
	STATIONS_FACING_CLOSURE_NAMES = [ "Belsize", "Bow", "Clerkenwell",
		"Downham", "Kingsland", "Knightsbridge", "Silvertown", "Southwark",
		"Westminster", "Woolwich" ],

    SIMPLIFIED_SQUARE_LATITUDE_SIZE = 0.001,
    SIMPLIFIED_SQUARE_LONGITUDE_SIZE = 0.0015,

    incidentsData = [ ];

// TODO: is what you see below necessary? why does d3.csv import all columns as strings?
var forceColumnsToFloat = function (columnNames, a) {
	_.each(a, function (record) {
		_.each(columnNames, function (columnName) {
			record[columnName] = parseFloat(record[columnName]);
		});
	});
}

var loadAllIncidents = function (callback) {
	log("Loading incident data...");
	incidentsData = [ ];
    csv()
        .from.stream(fs.createReadStream(__dirname + '/data/incidents.csv'), {
            columns: true
        })
        .on('record', function (row, index) {
        	incidentsData.push(row);
        })
        .on('end', function (count) {
			incidentsDataBoroughs = BOROUGHS_NAMES;
			forceColumnsToFloat([ 'firstPumpTime', 'simplifiedLatitude', 'simplifiedLongitude', 'footfall' ], incidentsData);
			log("Completed loading incident data.");
			if (callback) callback (null);
        });
};

var mean = function (values) {
	return _.reduce(values, function (memo, num) { return memo + num; }, 0.0) / values.length;
}

var median = function (values) {
	// Thanks to http://caseyjustus.com/finding-the-median-of-an-array-with-javascript
    values.sort(function(a,b) { return a - b; });
    var half = Math.floor(values.length / 2);
    return (values.length % 2 == 0) ? values[half] : (values[half - 1] + values[half]) / 2.0;
}

var getBoroughsByFirstResponderM = _.memoize(function () {
	results = { };
	var stations = _.unique(_.map(incidentsData, function (i) { return i.firstPumpStation; })).sort();
	_.each(stations, function (s) {
		results[s] = _.unique(_.map(_.filter(incidentsData, function (i) { return s == i.firstPumpStation; }), function (i) { return i.borough; })).sort();
	})
	return results;
});

var getBoroughResponseTimesM = _.memoize(function (borough, closedStations) {

	// Estimates the response time of a generic incident in a square; it expects
	// incidentsNotImpacted to be an array of incidents not impacted from the
	// stations closure, hence relevant for calculation
	var estimateSquareResponseTime = _.memoize(function (longitude, latitude) {
		var MIN_NO_OF_INCIDENTS = 1;
		var results = [ ];
		var foundEnough = false;
		for (var m = 0; !foundEnough; m++) {
			results = _.filter(incidentsNotImpacted, function (i) {
				return (i.simplifiedLongitude >= longitude - m * SIMPLIFIED_SQUARE_LONGITUDE_SIZE) &&
				(i.simplifiedLongitude < longitude + (m + 1) * SIMPLIFIED_SQUARE_LONGITUDE_SIZE) &&
				(i.simplifiedLatitude <= latitude + m * SIMPLIFIED_SQUARE_LATITUDE_SIZE) &&
				(i.simplifiedLatitude > latitude - (m + 1) * SIMPLIFIED_SQUARE_LATITUDE_SIZE);
			});
			foundEnough = results.length >= MIN_NO_OF_INCIDENTS;
		}
		return mean(_.map(results, function (i) { return i.firstPumpTime; }));
	}, function (longitude, latitude) {
		return longitude + '_' + latitude;
	});

	closedStations = ([ ].concat(closedStations));
	log("Calculating for the first time getBoroughResponseTimesM for " + borough + " with closed stations: " + closedStations.join(", "));
	var boroughIncidents = _.filter(incidentsData, function (i) { return i.borough == borough; }),
		incidentsNotImpacted = _.filter(boroughIncidents, function (i) { return !_.contains(closedStations, i.firstPumpStation); }),
		incidentsImpacted = _.filter(boroughIncidents, function (i) { return _.contains(closedStations, i.firstPumpStation); }),
		oldTimings = _.map(incidentsNotImpacted, function (i) { return i.firstPumpTime; }),
		newTimings = _.reduce(_.values(_.groupBy(incidentsImpacted, function (i) { return i.simplifiedLongitude + '_' + i.simplifiedLatitude; })),
			function (memo, incidentsInSameSquare) {
				var newResponseTime = estimateSquareResponseTime(incidentsInSameSquare[0].simplifiedLongitude, incidentsInSameSquare[0].simplifiedLatitude, closedStations);
				// See http://stackoverflow.com/a/19290390/1218376 for the strange expression below
				return memo.concat(_.map(Array(incidentsInSameSquare.length + 1).join(1).split(''), function() { return newResponseTime; }));
			},
			[ ]);
	return oldTimings.concat(newTimings);
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

var getBoroughResponseTimes = function (borough, closedStations, callback) {
	callback(null, getBoroughResponseTimesM(borough, closedStations));
};

var getBoroughHistM = _.memoize(function (borough, closedStations) {
	/* [{timeMin: 0, timeMax: 30, incidents: 5},{timeMin: 30, timeMax: 60, incidents: 7}, ...] */
	closedStations = ([ ].concat(closedStations));
	log("Calculating for the first time getBoroughHistM for " + borough + " with closed stations: " + closedStations.join(", "));
	var BIN_SIZE = 60, // seconds
		responseTimes = getBoroughResponseTimesM(borough, closedStations),
		maxResponseTime = Math.max.apply(null, responseTimes),
		results = [ ];
	for (var timeMin = 0; timeMin <= maxResponseTime; timeMin += BIN_SIZE) {
		var timeMax = timeMin + BIN_SIZE;
		results.push({
			timeMin: timeMin,
			timeMax: timeMax,
			incidents: _.filter(responseTimes, function (r) { return (r >= timeMin) && (r < timeMax); }).length,
		});
	}
	return results;
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

var getBoroughHist = function (borough, closedStations, callback) {
	callback(null, getBoroughHistM(borough, closedStations));
};

var getBoroughResponseTimeM = _.memoize(function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations));
	return mean(getBoroughResponseTimesM(borough, closedStations));
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

/*  The function loads the necessary detailed incident data, calculates the
    specified borough's response time and then calls back
    callback(err, boroughResponseTime) */
var getBoroughResponseTime = function (borough, closedStations, callback) {
	callback(null, getBoroughResponseTimeM(borough, closedStations));
};

/* Like getBoroughScore below, but assumes that the incidents data for
   the borough has been loaded already */
var getBoroughScoreM = _.memoize(function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations));
	log("Calculating for the first time getBoroughScoreM for " + borough + " with closed stations: " + closedStations.join(", "));
	var A = 0.75,
		medianResponseTimes = median(_.map(getBoroughResponseTimesM(borough, closedStations), function (x) { return x / 60; })),
		medianFootfall = median(_.map(_.filter(incidentsData, function (i) { return i.borough == borough; }), function (i) { return i.footfall; }));
	return Math.pow(medianResponseTimes, A) *
		Math.pow(Math.log(medianFootfall + 2) / Math.log(10), 1 - A);
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

/*  The function loads the necessary detailed incident data, calculates the
    specified borough's score vs time and population and then calls back
    callback(err, boroughscore) */
var getBoroughScore = function (borough, closedStations, callback) {
	callback(null, getBoroughScoreM(borough, closedStations));
};

// Just for testing, prints out a .csv on the JavaScript console
var getAllBoroughsScoresM = _.memoize(function (closedStations) {
	if (!closedStations) closedStations = STATIONS_FACING_CLOSURE_NAMES;
	var results = [ ];
	_.each(BOROUGHS_NAMES, function (borough) {
		results.push({
			borough: borough,
			responseTimeOpen: getBoroughResponseTimeM(borough),
			responseTimeClosed: getBoroughResponseTimeM(borough, closedStations),
			scoreOpen: getBoroughScoreM(borough),
			scoreClosed: getBoroughScoreM(borough, closedStations),
			medianFootfall: median(_.map(_.filter(incidentsData, function (i) { return i.borough == borough; }), function (i) { return i.footfall; })),
		});
	});
	return results;
}, function (closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return closedStations.join('_');
});

// Just for testing, prints out a .csv on the JavaScript console
var getAllBoroughsScoresCSV = function (closedStations) {
	log("It can take a while when calculated for the first time...");
	var results = getAllBoroughsScoresM(closedStations);
	console.log(_.keys(results[0]).join(","));
	_.each(results, function (r) {
		console.log(_.values(r).join(","));
	});
};

var log = function (s) {
	var entryDate = new Date();
	console.log(entryDate.getFullYear() + "/" + (entryDate.getMonth() < 9 ? '0' : '') + (entryDate.getMonth() + 1) + "/" + (entryDate.getDate() < 10 ? '0' : '') + entryDate.getDate() + " " + (entryDate.getHours() < 10 ? '0' : '') + entryDate.getHours() + ":" + (entryDate.getMinutes() < 10 ? '0' : '') + entryDate.getMinutes() + ":" + (entryDate.getSeconds() < 10 ? '0' : '') + entryDate.getSeconds() + " - " + s);
}

var server = restify.createServer({
  name: 'ODI - FNR Analysis server',
});

server.use(restify.queryParser());

server.get('/getBoroughsByFirstResponder', function (req, res, next) {
	res.send(200, getBoroughsByFirstResponderM());
	return next();
});

server.get('/getBoroughResponseTime', function (req, res, next) {
	res.send(200, getBoroughResponseTimeM(req.query.borough, req.query.close));
	return next();
});

server.get('/getBoroughScore', function (req, res, next) {
	res.send(200, getBoroughScoreM(req.query.borough, req.query.close));
	return next();
});

server.get('/getAllBoroughsScores', function (req, res, next) {
	res.send(200, getAllBoroughsScoresM(req.query.close));
	return next();
});

server.get('/getBoroughHist', function (req, res, next) {
	res.send(200, getBoroughHistM(req.query.borough, req.query.close));
	return next();
});

loadAllIncidents(function () {
	server.listen(8080);
});
<commit_msg>Added --port and --cache command line options<commit_after>var _ = require('underscore'),
	csv = require('csv'),
	fs = require('fs'),
    argv = require('optimist')
		.usage('Usage: $0 --port portNumber [--cache]')
		.demand([ 'port' ])
		.alias('cache', 'c')
		.alias('port', 'p')
		.argv,
	restify = require('restify'),

	BOROUGHS_NAMES = [ "Barking and Dagenham", "Barnet", "Bexley", "Brent",
		"Bromley", "Camden", "City of London", "Croydon", "Ealing", "Enfield",
		"Greenwich", "Hackney", "Hammersmith and Fulham", "Haringey", "Harrow",
		"Havering", "Hillingdon", "Hounslow", "Islington",
		"Kensington and Chelsea", "Kingston upon Thames", "Lambeth", "Lewisham",
		"Merton", "Newham", "Redbridge", "Richmond upon Thames", "Southwark",
		"Sutton", "Tower Hamlets", "Waltham Forest", "Wandsworth",
		"Westminster" ],

	// At the moment of writing, and according to the colour levels we are
	// currently using for the legend, of the statons facing closure Southwark is the
	// only single station closure that produces a visible effect on the map
	STATIONS_FACING_CLOSURE_NAMES = [ "Belsize", "Bow", "Clerkenwell",
		"Downham", "Kingsland", "Knightsbridge", "Silvertown", "Southwark",
		"Westminster", "Woolwich" ],

    SIMPLIFIED_SQUARE_LATITUDE_SIZE = 0.001,
    SIMPLIFIED_SQUARE_LONGITUDE_SIZE = 0.0015,

    incidentsData = [ ];

// TODO: what you see below necessary is necessary with D3, as all columns are
// imported as string for some reason not yet investigated
var forceColumnsToFloat = function (columnNames, a) {
	_.each(a, function (record) {
		_.each(columnNames, function (columnName) {
			record[columnName] = parseFloat(record[columnName]);
		});
	});
}

var loadAllIncidents = function (callback) {
	log("Loading incident data...");
	incidentsData = [ ];
    csv()
        .from.stream(fs.createReadStream(__dirname + '/data/incidents.csv'), {
            columns: true
        })
        .on('record', function (row, index) {
        	incidentsData.push(row);
        })
        .on('end', function (count) {
			incidentsDataBoroughs = BOROUGHS_NAMES;
			forceColumnsToFloat([ 'firstPumpTime', 'simplifiedLatitude', 'simplifiedLongitude', 'footfall' ], incidentsData);
			log("Completed loading incident data.");
			if (callback) callback (null);
        });
};

var mean = function (values) {
	return _.reduce(values, function (memo, num) { return memo + num; }, 0.0) / values.length;
}

var median = function (values) {
	// Thanks to http://caseyjustus.com/finding-the-median-of-an-array-with-javascript
    values.sort(function(a,b) { return a - b; });
    var half = Math.floor(values.length / 2);
    return (values.length % 2 == 0) ? values[half] : (values[half - 1] + values[half]) / 2.0;
}

var getBoroughsByFirstResponderM = _.memoize(function () {
	results = { };
	var stations = _.unique(_.map(incidentsData, function (i) { return i.firstPumpStation; })).sort();
	_.each(stations, function (s) {
		results[s] = _.unique(_.map(_.filter(incidentsData, function (i) { return s == i.firstPumpStation; }), function (i) { return i.borough; })).sort();
	})
	return results;
});

var getBoroughResponseTimesM = _.memoize(function (borough, closedStations) {

	// Estimates the response time of a generic incident in a square; it expects
	// incidentsNotImpacted to be an array of incidents not impacted from the
	// stations closure, hence relevant for calculation
	var estimateSquareResponseTime = _.memoize(function (longitude, latitude) {
		var MIN_NO_OF_INCIDENTS = 1;
		var results = [ ];
		var foundEnough = false;
		for (var m = 0; !foundEnough; m++) {
			results = _.filter(incidentsNotImpacted, function (i) {
				return (i.simplifiedLongitude >= longitude - m * SIMPLIFIED_SQUARE_LONGITUDE_SIZE) &&
				(i.simplifiedLongitude < longitude + (m + 1) * SIMPLIFIED_SQUARE_LONGITUDE_SIZE) &&
				(i.simplifiedLatitude <= latitude + m * SIMPLIFIED_SQUARE_LATITUDE_SIZE) &&
				(i.simplifiedLatitude > latitude - (m + 1) * SIMPLIFIED_SQUARE_LATITUDE_SIZE);
			});
			foundEnough = results.length >= MIN_NO_OF_INCIDENTS;
		}
		return mean(_.map(results, function (i) { return i.firstPumpTime; }));
	}, function (longitude, latitude) {
		return longitude + '_' + latitude;
	});

	closedStations = ([ ].concat(closedStations));
	log("Calculating for the first time getBoroughResponseTimesM for " + borough + " with closed stations: " + closedStations.join(", "));
	var boroughIncidents = _.filter(incidentsData, function (i) { return i.borough == borough; }),
		incidentsNotImpacted = _.filter(boroughIncidents, function (i) { return !_.contains(closedStations, i.firstPumpStation); }),
		incidentsImpacted = _.filter(boroughIncidents, function (i) { return _.contains(closedStations, i.firstPumpStation); }),
		oldTimings = _.map(incidentsNotImpacted, function (i) { return i.firstPumpTime; }),
		newTimings = _.reduce(_.values(_.groupBy(incidentsImpacted, function (i) { return i.simplifiedLongitude + '_' + i.simplifiedLatitude; })),
			function (memo, incidentsInSameSquare) {
				var newResponseTime = estimateSquareResponseTime(incidentsInSameSquare[0].simplifiedLongitude, incidentsInSameSquare[0].simplifiedLatitude, closedStations);
				// See http://stackoverflow.com/a/19290390/1218376 for the strange expression below
				return memo.concat(_.map(Array(incidentsInSameSquare.length + 1).join(1).split(''), function() { return newResponseTime; }));
			},
			[ ]);
	return oldTimings.concat(newTimings);
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

var getBoroughResponseTimes = function (borough, closedStations, callback) {
	callback(null, getBoroughResponseTimesM(borough, closedStations));
};

var getBoroughHistM = _.memoize(function (borough, closedStations) {
	/* [{timeMin: 0, timeMax: 30, incidents: 5},{timeMin: 30, timeMax: 60, incidents: 7}, ...] */
	closedStations = ([ ].concat(closedStations));
	log("Calculating for the first time getBoroughHistM for " + borough + " with closed stations: " + closedStations.join(", "));
	var BIN_SIZE = 60, // seconds
		responseTimes = getBoroughResponseTimesM(borough, closedStations),
		maxResponseTime = Math.max.apply(null, responseTimes),
		results = [ ];
	for (var timeMin = 0; timeMin <= maxResponseTime; timeMin += BIN_SIZE) {
		var timeMax = timeMin + BIN_SIZE;
		results.push({
			timeMin: timeMin,
			timeMax: timeMax,
			incidents: _.filter(responseTimes, function (r) { return (r >= timeMin) && (r < timeMax); }).length,
		});
	}
	return results;
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

var getBoroughHist = function (borough, closedStations, callback) {
	callback(null, getBoroughHistM(borough, closedStations));
};

var getBoroughResponseTimeM = _.memoize(function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations));
	return mean(getBoroughResponseTimesM(borough, closedStations));
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

/*  The function loads the necessary detailed incident data, calculates the
    specified borough's response time and then calls back
    callback(err, boroughResponseTime) */
var getBoroughResponseTime = function (borough, closedStations, callback) {
	callback(null, getBoroughResponseTimeM(borough, closedStations));
};

/* Like getBoroughScore below, but assumes that the incidents data for
   the borough has been loaded already */
var getBoroughScoreM = _.memoize(function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations));
	log("Calculating for the first time getBoroughScoreM for " + borough + " with closed stations: " + closedStations.join(", "));
	var A = 0.75,
		medianResponseTimes = median(_.map(getBoroughResponseTimesM(borough, closedStations), function (x) { return x / 60; })),
		medianFootfall = median(_.map(_.filter(incidentsData, function (i) { return i.borough == borough; }), function (i) { return i.footfall; }));
	return Math.pow(medianResponseTimes, A) *
		Math.pow(Math.log(medianFootfall + 2) / Math.log(10), 1 - A);
}, function (borough, closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return borough + (closedStations.length > 0 ? '-minus-' + closedStations.join('_') : '');
});

/*  The function loads the necessary detailed incident data, calculates the
    specified borough's score vs time and population and then calls back
    callback(err, boroughscore) */
var getBoroughScore = function (borough, closedStations, callback) {
	callback(null, getBoroughScoreM(borough, closedStations));
};

// Just for testing, prints out a .csv on the JavaScript console
var getAllBoroughsScoresM = _.memoize(function (closedStations) {
	if (!closedStations) closedStations = STATIONS_FACING_CLOSURE_NAMES;
	var results = [ ];
	_.each(BOROUGHS_NAMES, function (borough) {
		results.push({
			borough: borough,
			responseTimeOpen: getBoroughResponseTimeM(borough),
			responseTimeClosed: getBoroughResponseTimeM(borough, closedStations),
			scoreOpen: getBoroughScoreM(borough),
			scoreClosed: getBoroughScoreM(borough, closedStations),
			medianFootfall: median(_.map(_.filter(incidentsData, function (i) { return i.borough == borough; }), function (i) { return i.footfall; })),
		});
	});
	return results;
}, function (closedStations) {
	closedStations = ([ ].concat(closedStations)).sort();
	return closedStations.join('_');
});

// Just for testing, prints out a .csv on the JavaScript console
var getAllBoroughsScoresCSV = function (closedStations) {
	log("It can take a while when calculated for the first time...");
	var results = getAllBoroughsScoresM(closedStations);
	console.log(_.keys(results[0]).join(","));
	_.each(results, function (r) {
		console.log(_.values(r).join(","));
	});
};

var log = function (s) {
	var entryDate = new Date();
	console.log(entryDate.getFullYear() + "/" + (entryDate.getMonth() < 9 ? '0' : '') + (entryDate.getMonth() + 1) + "/" + (entryDate.getDate() < 10 ? '0' : '') + entryDate.getDate() + " " + (entryDate.getHours() < 10 ? '0' : '') + entryDate.getHours() + ":" + (entryDate.getMinutes() < 10 ? '0' : '') + entryDate.getMinutes() + ":" + (entryDate.getSeconds() < 10 ? '0' : '') + entryDate.getSeconds() + " - " + s);
}

var server = restify.createServer({
  name: 'ODI - FNR Analysis server',
});

server.use(restify.queryParser());

server.get('/getBoroughsByFirstResponder', function (req, res, next) {
	res.send(200, getBoroughsByFirstResponderM());
	return next();
});

server.get('/getBoroughResponseTime', function (req, res, next) {
	res.send(200, getBoroughResponseTimeM(req.query.borough, req.query.close));
	return next();
});

server.get('/getBoroughScore', function (req, res, next) {
	res.send(200, getBoroughScoreM(req.query.borough, req.query.close));
	return next();
});

server.get('/getAllBoroughsScores', function (req, res, next) {
	res.send(200, getAllBoroughsScoresM(req.query.close));
	return next();
});

server.get('/getBoroughHist', function (req, res, next) {
	res.send(200, getBoroughHistM(req.query.borough, req.query.close));
	return next();
});

loadAllIncidents(function () {
	if (argv.cache) {
		log("Caching getBoroughsByFirstResponderM()...");
		getBoroughsByFirstResponderM()
		log("Caching getBoroughResponseTimeM(borough) for all boroughs...");
		_.each(BOROUGHS_NAMES, function (b) { getBoroughResponseTimeM(b) });
		log("Caching getBoroughResponseTimeM(borough, closed stations) for all boroughs and the stations selected by the Mayor...");
		_.each(BOROUGHS_NAMES, function (b) { getBoroughResponseTimeM(b, STATIONS_FACING_CLOSURE_NAMES) });
		log("Caching getBoroughScoreM(borough) for all boroughs...");
		_.each(BOROUGHS_NAMES, function (b) { getBoroughScoreM(b) });
		log("Caching getBoroughScoreM(borough, closed stations) for all boroughs and the stations selected by the Mayor...");
		_.each(BOROUGHS_NAMES, function (b) { getBoroughScoreM(b, STATIONS_FACING_CLOSURE_NAMES) });
		log("Caching getBoroughHistM(borough) for all boroughs...");
		_.each(BOROUGHS_NAMES, function (b) { getBoroughHistM(b) });
		log("Caching getBoroughHistM(borough, closed stations) for all boroughs and the stations selected by the Mayor...");
		_.each(BOROUGHS_NAMES, function (b) { getBoroughHistM(b, STATIONS_FACING_CLOSURE_NAMES) });
		log("Caching getAllBoroughsScoresM()...");
		getAllBoroughsScoresM();
		log("Caching completed.");
	}
	server.listen(argv.port | 8080);
});
<|endoftext|>


<commit_before>var logLevelInputFactory = function (item) {
    return '' + item.name + '' +
        '<select class="config-item" data-field="' + item.name + '">' +
        '<option value="">None</option>' +
        '<option value="error">Error</option>' +
        '<option value="warning">Warning</option>' +
        '<option value="info">Info</option>' +
        '<option value="debug">Debug</option>' +
        '</select>' +
        '' + item.help + '';

};

var inputFieldFactory = function (item) {
    return '' + item.name + '' +
        '<input type="text" class="pure-input-1 config-item" id="' + item.name + '" name="' + item.name + '" data-field="' + item.name + '">' +
        '' + item.help + '';
};

var passwordFieldFactory = function (item) {
    return '' + item.name + '' +
        '<input type="password" class="pure-input-1 config-item" id="' + item.name + '" name="' + item.name + '" data-field="' + item.name + '">' +
        '' + item.help + '';
};

var checkboxFactory = function (item) {
    return '' +
        '<input class=" config-item " type="checkbox" value="' + item.name + '" data-field="' + item.name + '" name="' + item.name + '"> ' +
        item.name +
        '' + item.help + '' +
        '';
};

var legendFactory = function (item) {
    return '<legend>' + item.name + '</legend>'
};

var protocolInputField = function (item) {
    return '

';
};

var inputApply = function (item_id, data) {
    $('.config-item[data-field="' + item_id + '"]').val(data);
};

var checkboxApply = function (item_id, data) {
    $('.config-item[data-field="' + item_id + '"]').prop("checked", data);
};

var protocolApply = function (item_id, data) {

    var fillProtocolData = function (protos) {
        $("#rfProtocols").empty();
        protos.forEach(function (value) {
            var elem = '' +
                '<input class=" config-item protocols-item" id="proto_check_' + value + '" type="checkbox" value="' + value + '" data-field="' + item_id + '" name="' + item_id + '">' +
                ' Protocol ' + value +
                '';
            $("#rfProtocols").append(elem);
            registerConfigUi('#proto_check_' + value);
        });
        if (data.length > 0) {
            data.forEach(function (value) {
                $('#proto_check_' + value).prop('checked', true);
            });
        } else {
            $(".protocols-item").each(function (_, value) {
                $(value).prop("checked", true);
            });
        }
    };

    $.ajax({
               url: "/protocols",
               type: "GET",
               contentType: 'application/json',
               success: fillProtocolData
           });
};

var inputGet = function (item_id) {
    return $('.config-item[data-field="' + item_id + '"]').val();
};

var inputGetInt = function (item_id) {
    return parseInt(inputGet(item_id));
};

var checkboxGet = function (item_id) {
    return $('.config-item[data-field="' + item_id + '"]').prop("checked");
};

var protocolGet = function (item_id) {
    var checked = $('.protocols-item:checked');
    if ($('.protocols-item').length === checked.length) {
        return [];
    }
    return $.map(checked, function (x) {
        return $(x).val()
    })
};

function ConfigItem(name, factory, apply, fetch, help) {
    this.name = name;
    this.factory = factory;
    this.apply = apply;
    this.fetch = fetch;
    this.help = help;
}

function GroupItem(name, factory) {
    this.name = name;
    this.factory = factory;
}

var CONFIG_ITEMS = [
    new GroupItem("General Config", legendFactory),
    new ConfigItem("deviceName", inputFieldFactory, inputApply, inputGet, "The general name of the device"),
    new ConfigItem("mdnsName", inputFieldFactory, inputApply, inputGet, "The name the device shall announce via mdns"),
    new ConfigItem("configPassword", passwordFieldFactory, inputApply, inputGet, "The admin password for the web UI"),

    new GroupItem("MQTT Config", legendFactory),
    new ConfigItem("mqttBroker", inputFieldFactory, inputApply, inputGet, "MQTT Broker host"),
    new ConfigItem("mqttBrokerPort", inputFieldFactory, inputApply, inputGetInt, "MQTT Broker port"),
    new ConfigItem("mqttUser", inputFieldFactory, inputApply, inputGet, "MQTT username (optional)"),
    new ConfigItem("mqttPassword", passwordFieldFactory, inputApply, inputGet, "MQTT password (optional)"),
    new ConfigItem("mqttRetain", checkboxFactory, checkboxApply, checkboxGet, "Retain MQTT messages"),

    new GroupItem("MQTT Topic Config", legendFactory),
    new ConfigItem("mqttReceiveTopic", inputFieldFactory, inputApply, inputGet, "Topic to publish received signal"),
    new ConfigItem("mqttSendTopic", inputFieldFactory, inputApply, inputGet, "Topic to get signals to send from"),

    new GroupItem("433MHz RF Config", legendFactory),
    new ConfigItem("rfEchoMessages", checkboxFactory, checkboxApply, checkboxGet, "Echo sent rf messages back"),
    new ConfigItem("rfReceiverPin", inputFieldFactory, inputApply, inputGetInt, "The pin used for the rf receiver"),
    new ConfigItem("rfTransmitterPin", inputFieldFactory, inputApply, inputGetInt, "The pin used for the RF transmitter"),

    new GroupItem("Enabled RF protocols", legendFactory),
    new ConfigItem("rfProtocols", protocolInputField, protocolApply, protocolGet, ""),

    new GroupItem("Log Config", legendFactory),
    new ConfigItem("serialLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for serial logging"),
    new ConfigItem("webLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for logging to the web UI"),
    new ConfigItem("syslogLevel", logLevelInputFactory, inputApply, inputGet, "Level for syslog logging"),
    new ConfigItem("syslogHost", inputFieldFactory, inputApply, inputGet, "Syslog server (optional)"),
    new ConfigItem("syslogPort", inputFieldFactory, inputApply, inputGetInt, "Syslog port (optional)")
];

var ui_map = {};
CONFIG_ITEMS.forEach(function (value) {
    if (value.fetch !== undefined) {
        ui_map[value.name] = value;
    }
});

var webSocket = new WebSocket("ws://" + location.hostname + ":81");
webSocket.onmessage = function (e) {
    var message = e.data;
    var container = $('#log-container');
    var pre = container.find('pre');
    pre.append(message);
    if (message === '\n' || (typeof message === "string" && message.indexOf('\n') !== -1)) {
        container.scrollTop(pre.get(0).scrollHeight);
    }
};

var last_cfg = {};
var changes = {};

function throttle(callback, limit) {
    var wait = false;
    return function (args) {
        if (!wait) {
            callback.apply(this, arguments);
            wait = true;
            setTimeout(function () {
                wait = false;
            }, limit);
        }
    }
}

var registerConfigUi = function (item) {
    var _item = $(item);
    _item.change(function () {
        var name = _item.data("field");
        var new_data = ui_map[name].fetch(name);
        if (JSON.stringify(last_cfg[name]) !== JSON.stringify(new_data)) {
            changes[name] = new_data;
        }
    });
};

var loadConfig = function () {
    var applyConfig = function (data) {
        $.each(data, function (key, value) {
            ui_map[key].apply(key, value);
        });
        changes = {};
    };

    $.ajax({
               url: '/config',
               type: 'GET',
               contentType: 'application/json',
               success: applyConfig
           });
};

var sendCommand = throttle(
    function (params) {
        $.ajax({
                   url: '/system',
                   type: 'POST',
                   data: JSON.stringify(params),
                   contentType: 'application/json'
               }
        );
    },
    1000
);

var initUi = function () {
    var settings = "";
    CONFIG_ITEMS.forEach(function (item) {
        settings += item.factory(item);
    });
    $("#settings").prepend(settings);
    CONFIG_ITEMS.forEach(function (item) {
        registerConfigUi('.config-item[data-field="' + item.name + '"]')
    });
    loadConfig();
    loadDebug();
};

var loadDebug = function () {
    var applyDebug = function (data) {
        $.each(data, function (key, value) {
            $('.debug-item[data-dbg-field="' + key + '"]').prop("checked", value);
        });
    };

    $.ajax({
               url: "/debug",
               type: "GET",
               contentType: 'application/json',
               success: applyDebug
           });
};

var saveDebug = function () {
    var data = {};
    $(".debug-item").each(function (_, item) {
        var _item = $(item);
        data[_item.attr("name")] = _item.prop("checked");
    });

    $.ajax({
               url: '/debug',
               type: "PUT",
               data: JSON.stringify(data),
               contentType: 'application/json',
               success: loadConfig
           });
};

$(function () {
    $('.system-btn').click(function () {
        sendCommand({command: $(this).data('command')});
    });

    $('#settings-form').submit(function (e) {
        e.preventDefault();

        $.ajax({
                   url: "/config",
                   type: 'PUT',
                   contentType: 'application/json',
                   data: JSON.stringify(changes),
                   success: loadConfig

               });

        return false;
    });

    $('#cfg-form-reset').click(function (e) {
        e.preventDefault();
        loadConfig();
        return false;
    });

    $("#debugging-form").submit(function (e) {
        e.preventDefault();

        saveDebug();
        return false;
    });

    $('#dbg-form-reset').click(function (e) {
        e.preventDefault();
        loadDebug();
        return false;
    });

    initUi();
});
<commit_msg>Frontend: Handle reboots and changes of system commands.<commit_after>var logLevelInputFactory = function (item) {
    return '' + item.name + '' +
        '<select class="config-item" data-field="' + item.name + '">' +
        '<option value="">None</option>' +
        '<option value="error">Error</option>' +
        '<option value="warning">Warning</option>' +
        '<option value="info">Info</option>' +
        '<option value="debug">Debug</option>' +
        '</select>' +
        '' + item.help + '';

};

var inputFieldFactory = function (item) {
    return '' + item.name + '' +
        '<input type="text" class="pure-input-1 config-item" id="' + item.name + '" name="' + item.name + '" data-field="' + item.name + '">' +
        '' + item.help + '';
};

var passwordFieldFactory = function (item) {
    return '' + item.name + '' +
        '<input type="password" class="pure-input-1 config-item" id="' + item.name + '" name="' + item.name + '" data-field="' + item.name + '">' +
        '' + item.help + '';
};

var checkboxFactory = function (item) {
    return '' +
        '<input class=" config-item " type="checkbox" value="' + item.name + '" data-field="' + item.name + '" name="' + item.name + '"> ' +
        item.name +
        '' + item.help + '' +
        '';
};

var legendFactory = function (item) {
    return '<legend>' + item.name + '</legend>'
};

var protocolInputField = function (item) {
    return '

';
};

var inputApply = function (item_id, data) {
    $('.config-item[data-field="' + item_id + '"]').val(data);
};

var checkboxApply = function (item_id, data) {
    $('.config-item[data-field="' + item_id + '"]').prop("checked", data);
};

var protocolApply = function (item_id, data) {

    var fillProtocolData = function (protos) {
        $("#rfProtocols").empty();
        protos.forEach(function (value) {
            var elem = '' +
                '<input class=" config-item protocols-item" id="proto_check_' + value + '" type="checkbox" value="' + value + '" data-field="' + item_id + '" name="' + item_id + '">' +
                ' Protocol ' + value +
                '';
            $("#rfProtocols").append(elem);
            registerConfigUi('#proto_check_' + value);
        });
        if (data.length > 0) {
            data.forEach(function (value) {
                $('#proto_check_' + value).prop('checked', true);
            });
        } else {
            $(".protocols-item").each(function (_, value) {
                $(value).prop("checked", true);
            });
        }
    };

    $.ajax({
               url: "/protocols",
               type: "GET",
               contentType: 'application/json',
               success: fillProtocolData
           });
};

var inputGet = function (item_id) {
    return $('.config-item[data-field="' + item_id + '"]').val();
};

var inputGetInt = function (item_id) {
    return parseInt(inputGet(item_id));
};

var checkboxGet = function (item_id) {
    return $('.config-item[data-field="' + item_id + '"]').prop("checked");
};

var protocolGet = function (item_id) {
    var checked = $('.protocols-item:checked');
    if ($('.protocols-item').length === checked.length) {
        return [];
    }
    return $.map(checked, function (x) {
        return $(x).val()
    })
};

function ConfigItem(name, factory, apply, fetch, help) {
    this.name = name;
    this.factory = factory;
    this.apply = apply;
    this.fetch = fetch;
    this.help = help;
}

function GroupItem(name, factory) {
    this.name = name;
    this.factory = factory;
}

var CONFIG_ITEMS = [
    new GroupItem("General Config", legendFactory),
    new ConfigItem("deviceName", inputFieldFactory, inputApply, inputGet, "The general name of the device"),
    new ConfigItem("mdnsName", inputFieldFactory, inputApply, inputGet, "The name the device shall announce via mdns"),
    new ConfigItem("configPassword", passwordFieldFactory, inputApply, inputGet, "The admin password for the web UI"),

    new GroupItem("MQTT Config", legendFactory),
    new ConfigItem("mqttBroker", inputFieldFactory, inputApply, inputGet, "MQTT Broker host"),
    new ConfigItem("mqttBrokerPort", inputFieldFactory, inputApply, inputGetInt, "MQTT Broker port"),
    new ConfigItem("mqttUser", inputFieldFactory, inputApply, inputGet, "MQTT username (optional)"),
    new ConfigItem("mqttPassword", passwordFieldFactory, inputApply, inputGet, "MQTT password (optional)"),
    new ConfigItem("mqttRetain", checkboxFactory, checkboxApply, checkboxGet, "Retain MQTT messages"),

    new GroupItem("MQTT Topic Config", legendFactory),
    new ConfigItem("mqttReceiveTopic", inputFieldFactory, inputApply, inputGet, "Topic to publish received signal"),
    new ConfigItem("mqttSendTopic", inputFieldFactory, inputApply, inputGet, "Topic to get signals to send from"),

    new GroupItem("433MHz RF Config", legendFactory),
    new ConfigItem("rfEchoMessages", checkboxFactory, checkboxApply, checkboxGet, "Echo sent rf messages back"),
    new ConfigItem("rfReceiverPin", inputFieldFactory, inputApply, inputGetInt, "The pin used for the rf receiver"),
    new ConfigItem("rfTransmitterPin", inputFieldFactory, inputApply, inputGetInt, "The pin used for the RF transmitter"),

    new GroupItem("Enabled RF protocols", legendFactory),
    new ConfigItem("rfProtocols", protocolInputField, protocolApply, protocolGet, ""),

    new GroupItem("Log Config", legendFactory),
    new ConfigItem("serialLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for serial logging"),
    new ConfigItem("webLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for logging to the web UI"),
    new ConfigItem("syslogLevel", logLevelInputFactory, inputApply, inputGet, "Level for syslog logging"),
    new ConfigItem("syslogHost", inputFieldFactory, inputApply, inputGet, "Syslog server (optional)"),
    new ConfigItem("syslogPort", inputFieldFactory, inputApply, inputGetInt, "Syslog port (optional)")
];

var ui_map = {};
CONFIG_ITEMS.forEach(function (value) {
    if (value.fetch !== undefined) {
        ui_map[value.name] = value;
    }
});

var webSocket = new WebSocket("ws://" + location.hostname + ":81");
webSocket.onmessage = function (e) {
    var message = e.data;
    var container = $('#log-container');
    var pre = container.find('pre');
    pre.append(message);
    if (message === '\n' || (typeof message === "string" && message.indexOf('\n') !== -1)) {
        container.scrollTop(pre.get(0).scrollHeight);
    }
};

var last_cfg = {};
var changes = {};

function throttle(callback, limit) {
    var wait = false;
    return function (args) {
        if (!wait) {
            callback.apply(this, arguments);
            wait = true;
            setTimeout(function () {
                wait = false;
            }, limit);
        }
    }
}

var registerConfigUi = function (item) {
    var _item = $(item);
    _item.change(function () {
        var name = _item.data("field");
        var new_data = ui_map[name].fetch(name);
        if (JSON.stringify(last_cfg[name]) !== JSON.stringify(new_data)) {
            changes[name] = new_data;
        }
    });
};

var loadConfig = function () {
    var applyConfig = function (data) {
        $.each(data, function (key, value) {
            ui_map[key].apply(key, value);
        });
        changes = {};
    };

    $.ajax({
               url: '/config',
               type: 'GET',
               contentType: 'application/json',
               success: applyConfig
           });
};

var SystemCommandActions = {
    restart: function () {
        var _body = $("body");
        _body.empty();
        _body.append("
Device will reboot!

Try to reconnect in 15 seconds.
");
        setTimeout(function () {
            window.location.reload(true);
        }, 15000);
    },
    reset_wifi: function () {
        var _body = $("body");
        _body.empty();
        _body.append("
Devices WIFI settings where cleared!

Please reconfigure it.
");
    },
    reset_config: function () {
        var _body = $("body");
        _body.empty();
        _body.append("
Devices Config was reset!

Reload page ...
");
        setTimeout(function () {
            window.location.reload(true);
        }, 2000);
    }
};

var sendCommand = throttle(
    function (params) {
        $.ajax({
                   url: '/system',
                   type: 'POST',
                   data: JSON.stringify(params),
                   contentType: 'application/json',
                   success: function () {
                       SystemCommandActions[params["command"]]();
                   }
               }
        );
    },
    1000
);

var initUi = function () {
    var settings = "";
    CONFIG_ITEMS.forEach(function (item) {
        settings += item.factory(item);
    });
    $("#settings").prepend(settings);
    CONFIG_ITEMS.forEach(function (item) {
        registerConfigUi('.config-item[data-field="' + item.name + '"]')
    });
    loadConfig();
    loadDebug();
};

var loadDebug = function () {
    var applyDebug = function (data) {
        $.each(data, function (key, value) {
            $('.debug-item[data-dbg-field="' + key + '"]').prop("checked", value);
        });
    };

    $.ajax({
               url: "/debug",
               type: "GET",
               contentType: 'application/json',
               success: applyDebug
           });
};

var saveDebug = function () {
    var data = {};
    $(".debug-item").each(function (_, item) {
        var _item = $(item);
        data[_item.attr("name")] = _item.prop("checked");
    });

    $.ajax({
               url: '/debug',
               type: "PUT",
               data: JSON.stringify(data),
               contentType: 'application/json',
               success: loadConfig
           });
};

$(function () {
    $('.system-btn').click(function () {
        sendCommand({command: $(this).data('command')});
    });

    $('#settings-form').submit(function (e) {
        e.preventDefault();

        $.ajax({
                   url: "/config",
                   type: 'PUT',
                   contentType: 'application/json',
                   data: JSON.stringify(changes),
                   success: loadConfig

               });

        return false;
    });

    $('#cfg-form-reset').click(function (e) {
        e.preventDefault();
        loadConfig();
        return false;
    });

    $("#debugging-form").submit(function (e) {
        e.preventDefault();

        saveDebug();
        return false;
    });

    $('#dbg-form-reset').click(function (e) {
        e.preventDefault();
        loadDebug();
        return false;
    });

    initUi();
});
<|endoftext|>


<commit_before>function Chloroform(imageUrl, options) {
  var o;

  o = this.options = options || {};

  o.count     || (o.count     = 3);
  o.subsample || (o.subsample = 0);

  this.imageUrl          = imageUrl;
  this.colors            = {};
  this.canvas            = document.createElement('canvas');
  this.canvas.width      = this.width;
  this.canvas.height     = this.height;
  this.ctx               = this.canvas.getContext('2d');
  this.image             = new Image();

  if (!/^data:/.test(this.imageUrl)) {
    this.image.crossOrigin = 'anonymous';
  }
}

base.Chloroform.prototype = {
  width:    100,
  height:   100,

  afterImageLoads: function(fn) {
    this.image.onload = fn.bind(this);
    this.image.src    = this.imageUrl;

    return this;
  },

  analyze: function(callback) {
    this.afterImageLoads(function(){
      this.analyzeNow();
      callback && callback(this.colors);
    });

    return this;
  },

  analyzeNow: function() {
    var colors, lastFoundColor;

    this.ctx.drawImage(this.image, 0, 0, this.width, this.height);

    this.colors.background = this.findBackground();
    this.colors.contrast   = this.isDark(this.colors.background) ? '255,255,255' : '0,0,0';
    lastFoundColor         = this.colors.contrast;

    colors = this.findColors(this.imageData(), {subsample: 1});

    for (var i = 0; i < this.options.count; i++) {
      lastFoundColor = this.colors[i] = colors[i] || lastFoundColor;
    }

    return this;
  },

  imageData: function(left, top, width, height) {
    left   || (left   = 0);
    top    || (top    = 0);
    width  || (width  = this.width);
    height || (height = this.height);

    return this.ctx.getImageData(left, top, width, height).data;
  },

  findBackground: function(size, offset) {
    var edgePixels;

    size   || (size   = 1);
    offset || (offset = 0);

    edgePixels = [];

    // top
    [].push.apply(edgePixels, this.imageData(
      offset,
      offset,
      this.width - offset * 2,
      size
    ));

      // right
    [].push.apply(edgePixels, this.imageData(
      this.width - offset - size,
      offset,
      size,
      this.height - offset * 2
    ));

      // bottom
    // [].push.apply(edgePixels, this.imageData(
    //   offset,
    //   this.height - offset - size,
    //   this.width - offset * 2,
    //   size
    // ));

    // left
    [].push.apply(edgePixels, this.imageData(
      offset,
      offset,
      size,
      this.height - offset * 2
    ));

    return this.findColors(edgePixels, { count: 1 })[0]
  },

  findColors: function(pixels, options) {
    var numberOfColors, diff, stepSize, subsample, counts, groupedCounts;

    options || (options = {});

    numberOfColors = options.count         || 3;
    diff           = options.startDistance || 0;
    stepSize       = options.stepBy        || 500;
    subsample      = options.subsample     || 0;

    counts = groupedCounts = this.countColors(pixels, subsample);

    for (var i = 0; i < 10; i++) {
      if (Object.keys(counts).length <= numberOfColors) {
        break;
      }

      counts = groupedCounts;
      groupedCounts = this.groupColorsByCount(counts, diff += stepSize);
    }

    return this.colorsSortedByCount(counts);
  },

  countColors: function(pixels, subsample) {
    var counts, step, key, rgb;

    counts = {};
    step = 4 * (subsample + 1);

    for (var i = 0; i < pixels.length; i += step) {
      rgb = [pixels[i], pixels[i + 1], pixels[i + 2]];

      if (this.colors.background && !this.contrastsBackground(rgb)) {
        continue;
      }

      key = rgb.join(',');

      counts[key] || (counts[key] = 0);
      counts[key]  += 1;
    }

    return counts;
  },

  colorsSortedByCount: function(counts) {
    return Object.keys(counts).sort(function(a, b) {
      return counts[b] - counts[a];
    });
  },

  groupColorsByCount: function(counts, diff) {
    var groups, colors, i, j, bucket;

    groups = {};
    colors = this.colorsSortedByCount(counts);
    i      = 0;

    while (bucket = colors[i++]) {
      groups[bucket] = counts[bucket];
      j = i;

      while (colors[j]) {
        if (this.areDiffering(bucket, colors[j], diff)) {
          j++;
        } else {
          groups[bucket] += counts[colors[j]];
          colors.splice(j, 1);
        }
      }
    }

    return groups;
  },

  ybr: function(rgb) {
    if (typeof rgb === 'string') {
      return this.ybr(rgb.split(','));
    }

    var r = rgb[0];
    var g = rgb[1];
    var b = rgb[2];

    var y  =       (0.299    * r) + (0.587    * g) + (0.114    * b);
    var cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5      * b);
    var cr = 128 + (0.5      * r) - (0.418688 * g) - (0.081312 * b);

    return [y, cb, cr];
  },

  distanceSquared: function(a, b) {
    var sum;

    a   = this.ybr(a);
    b   = this.ybr(b);

    sum  = Math.pow(a[0] - b[0], 2);
    sum += Math.pow(a[1] - b[1], 2);
    sum += Math.pow(a[2] - b[2], 2);

    return sum;
  },

  areDiffering: function(a, b, diff) {
    diff || (diff = 3000)
    return this.distanceSquared(a, b) > diff;
  },

  isDark: function(color) {
    return this.ybr(color)[0] < 127;
  },

  areContrasting: function(a, b, diff) {
    diff || (diff = 75)
    return Math.abs(this.ybr(a)[0] - this.ybr(b)[0]) > diff;
  },

  contrastsBackground: function(colorKey) {
    return this.areContrasting(this.colors.background, colorKey);
  }
}

Chloroform.analyze = function(imageUrl, options, callback) {
  if (!callback) {
    callback = options;
    options  = {};
  }

  return new Chloroform(imageUrl, options).analyze(callback);
}

if (typeof module !== 'undefined') {
  module.exports = Chloroform
}
<commit_msg>fix typo<commit_after>function Chloroform(imageUrl, options) {
  var o;

  o = this.options = options || {};

  o.count     || (o.count     = 3);
  o.subsample || (o.subsample = 0);

  this.imageUrl          = imageUrl;
  this.colors            = {};
  this.canvas            = document.createElement('canvas');
  this.canvas.width      = this.width;
  this.canvas.height     = this.height;
  this.ctx               = this.canvas.getContext('2d');
  this.image             = new Image();

  if (!/^data:/.test(this.imageUrl)) {
    this.image.crossOrigin = 'anonymous';
  }
}

Chloroform.prototype = {
  width:    100,
  height:   100,

  afterImageLoads: function(fn) {
    this.image.onload = fn.bind(this);
    this.image.src    = this.imageUrl;

    return this;
  },

  analyze: function(callback) {
    this.afterImageLoads(function(){
      this.analyzeNow();
      callback && callback(this.colors);
    });

    return this;
  },

  analyzeNow: function() {
    var colors, lastFoundColor;

    this.ctx.drawImage(this.image, 0, 0, this.width, this.height);

    this.colors.background = this.findBackground();
    this.colors.contrast   = this.isDark(this.colors.background) ? '255,255,255' : '0,0,0';
    lastFoundColor         = this.colors.contrast;

    colors = this.findColors(this.imageData(), {subsample: 1});

    for (var i = 0; i < this.options.count; i++) {
      lastFoundColor = this.colors[i] = colors[i] || lastFoundColor;
    }

    return this;
  },

  imageData: function(left, top, width, height) {
    left   || (left   = 0);
    top    || (top    = 0);
    width  || (width  = this.width);
    height || (height = this.height);

    return this.ctx.getImageData(left, top, width, height).data;
  },

  findBackground: function(size, offset) {
    var edgePixels;

    size   || (size   = 1);
    offset || (offset = 0);

    edgePixels = [];

    // top
    [].push.apply(edgePixels, this.imageData(
      offset,
      offset,
      this.width - offset * 2,
      size
    ));

      // right
    [].push.apply(edgePixels, this.imageData(
      this.width - offset - size,
      offset,
      size,
      this.height - offset * 2
    ));

      // bottom
    // [].push.apply(edgePixels, this.imageData(
    //   offset,
    //   this.height - offset - size,
    //   this.width - offset * 2,
    //   size
    // ));

    // left
    [].push.apply(edgePixels, this.imageData(
      offset,
      offset,
      size,
      this.height - offset * 2
    ));

    return this.findColors(edgePixels, { count: 1 })[0]
  },

  findColors: function(pixels, options) {
    var numberOfColors, diff, stepSize, subsample, counts, groupedCounts;

    options || (options = {});

    numberOfColors = options.count         || 3;
    diff           = options.startDistance || 0;
    stepSize       = options.stepBy        || 500;
    subsample      = options.subsample     || 0;

    counts = groupedCounts = this.countColors(pixels, subsample);

    for (var i = 0; i < 10; i++) {
      if (Object.keys(counts).length <= numberOfColors) {
        break;
      }

      counts = groupedCounts;
      groupedCounts = this.groupColorsByCount(counts, diff += stepSize);
    }

    return this.colorsSortedByCount(counts);
  },

  countColors: function(pixels, subsample) {
    var counts, step, key, rgb;

    counts = {};
    step = 4 * (subsample + 1);

    for (var i = 0; i < pixels.length; i += step) {
      rgb = [pixels[i], pixels[i + 1], pixels[i + 2]];

      if (this.colors.background && !this.contrastsBackground(rgb)) {
        continue;
      }

      key = rgb.join(',');

      counts[key] || (counts[key] = 0);
      counts[key]  += 1;
    }

    return counts;
  },

  colorsSortedByCount: function(counts) {
    return Object.keys(counts).sort(function(a, b) {
      return counts[b] - counts[a];
    });
  },

  groupColorsByCount: function(counts, diff) {
    var groups, colors, i, j, bucket;

    groups = {};
    colors = this.colorsSortedByCount(counts);
    i      = 0;

    while (bucket = colors[i++]) {
      groups[bucket] = counts[bucket];
      j = i;

      while (colors[j]) {
        if (this.areDiffering(bucket, colors[j], diff)) {
          j++;
        } else {
          groups[bucket] += counts[colors[j]];
          colors.splice(j, 1);
        }
      }
    }

    return groups;
  },

  ybr: function(rgb) {
    if (typeof rgb === 'string') {
      return this.ybr(rgb.split(','));
    }

    var r = rgb[0];
    var g = rgb[1];
    var b = rgb[2];

    var y  =       (0.299    * r) + (0.587    * g) + (0.114    * b);
    var cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5      * b);
    var cr = 128 + (0.5      * r) - (0.418688 * g) - (0.081312 * b);

    return [y, cb, cr];
  },

  distanceSquared: function(a, b) {
    var sum;

    a   = this.ybr(a);
    b   = this.ybr(b);

    sum  = Math.pow(a[0] - b[0], 2);
    sum += Math.pow(a[1] - b[1], 2);
    sum += Math.pow(a[2] - b[2], 2);

    return sum;
  },

  areDiffering: function(a, b, diff) {
    diff || (diff = 3000)
    return this.distanceSquared(a, b) > diff;
  },

  isDark: function(color) {
    return this.ybr(color)[0] < 127;
  },

  areContrasting: function(a, b, diff) {
    diff || (diff = 75)
    return Math.abs(this.ybr(a)[0] - this.ybr(b)[0]) > diff;
  },

  contrastsBackground: function(colorKey) {
    return this.areContrasting(this.colors.background, colorKey);
  }
}

Chloroform.analyze = function(imageUrl, options, callback) {
  if (!callback) {
    callback = options;
    options  = {};
  }

  return new Chloroform(imageUrl, options).analyze(callback);
}

if (typeof module !== 'undefined') {
  module.exports = Chloroform
}
<|endoftext|>


<commit_before>exports.getData = function (type, rootCallback) {
    if (type === 'all') {
        const fs = require('fs');
        let path = "/folderPath";

        fs.readdir(path, function (err, items) {
            console.log(items);
            for (let index = 0; index < items.length; index++) {
                console.log(items[index]);
            }
        });
    } else {
        try {
            const parser = require('./blog/' + type);
            parser.getData(function (result) {
                rootCallback(result);
            });
        } catch (exception) {
            const resultItem = require('./result_item');
            let data = resultItem.getResultItem();
            data.blog_name = 'NODATA';
            rootCallback(data);
        }
    }
}<commit_msg>JSON Array 개선<commit_after>exports.getData = function (type, rootCallback) {
    if (type === 'all') {
        let fs = require('fs');
        let path = process.cwd() + '/data/parser/blog';

        let blogList = fs.readdirSync(path);
        let result = JSON.parse('[]');
        let counter = 1;

        blogList.forEach(file => {
            let parser = require('./blog/' + file);
            parser.getData(function (data) {
                result.push(data);
                counter === blogList.length ? rootCallback(data) : counter++;
            });
        });
    } else {
        try {
            let parser = require('./blog/' + type);
            parser.getData(function (result) {
                rootCallback(result);
            });
        } catch (exception) {
            let resultItem = require('./result_item');
            let data = resultItem.getResultItem();
            data.blog_name = 'NODATA';
            rootCallback(data);
        }
    }
};<|endoftext|>


<commit_before>/*
 * CircleType 0.3
 * Peter Hrynkow
 * Copyright 2013, Licensed GPL & MIT
 *
*/

$.fn.circleType = function(options) {

    var settings = {
        dir: 1
    };

    if (typeof($.fn.lettering) !== 'function') {
        console.log('Lettering.js is required');
        return;
    }

    return this.each(function () {

        if (options) {
            $.extend(settings, options);
        }
        var elem = this,
            delta = (180 / Math.PI),
            txt = elem.innerHTML.replace(/\s/g, ' '),
            letters;

        elem.innerHTML = txt
        $(elem).lettering();

        elem.style.position =  'relative';

        letters = elem.getElementsByTagName('span');

        var layout = function () {
            var tw = 0,
                i,
                offset = 0,
                ch = parseInt($(elem).css('line-height'), 10),
                minRadius,
                origin,
                innerRadius,
                l, style, r, transform,
                bb,
                yMax = Number.MIN_VALUE,
                yMin = Number.MAX_VALUE;

            for (i = 0; i < letters.length; i++) {
                tw += letters[i].offsetWidth;
            }
            minRadius = (tw / Math.PI) / 2 + ch;

            if (settings.fluid) {
                settings.radius = Math.max(elem.offsetWidth / 2, minRadius);
            }
            else if (!settings.radius) {
                settings.radius = minRadius;
            }
            if (settings.dir === -1) {
                origin = 'center ' + (-settings.radius + ch) + 'px';
            } else {
                origin = 'center ' + settings.radius + 'px';
            }

            innerRadius = settings.radius - ch;

            for (i = 0; i < letters.length; i++) {
                l = letters[i];
                offset += l.offsetWidth / 2 / innerRadius * delta;
                l.rot = offset;
                offset += l.offsetWidth / 2 / innerRadius * delta;
            }
            for (i = 0; i < letters.length; i++) {
                l = letters[i]
                style = l.style
                r = (-offset * settings.dir / 2) + l.rot * settings.dir
                transform = 'rotate(' + r + 'deg)';

                style.position = 'absolute';
                style.left = '50%';
                style.marginLeft = -l.offsetWidth / 2 + 'px';

                style.webkitTransform = transform;
                style.MozTransform = transform;
                style.oTransform = transform;
                style.msTransform = transform;
                style.transform = transform;

                style.webkitTransformOrigin = origin;
                style.MozTransformOrigin = origin;
                style.oTransformOrigin = origin;
                style.msTransformOrigin = origin;
                style.transformOrigin = origin;
                if(settings.dir === -1) {
                    style.bottom = 0;
                }

                bb = getPosition(l);
                if (bb.top < yMin) {
                    yMin = bb.top;
                }
                if (bb.top > yMax) {
                    yMax = bb.top;
                }

            }
            elem.style.height = yMax - yMin + ch + 'px';
        };

        var getPosition = function (elem) {
        	var docElem = document.documentElement,
        	    box = elem.getBoundingClientRect();
	        return {
		        top: box.top + window.pageYOffset - docElem.clientTop,
		        left: box.left + window.pageXOffset - docElem.clientLeft
	        };
        };

        if (settings.fluid) {
            $(window).resize(function () {
                layout();
            });
        }

        if (document.readyState !== "complete") {
            elem.style.visibility = 'hidden';
            $(window).load(function () {
                elem.style.visibility = 'visible';
                layout();
            });
        } else {
            layout();
        }
    });
};<commit_msg>removing tab characters<commit_after>/*
 * CircleType 0.3
 * Peter Hrynkow
 * Copyright 2013, Licensed GPL & MIT
 *
*/

$.fn.circleType = function(options) {

    var settings = {
        dir: 1
    };

    if (typeof($.fn.lettering) !== 'function') {
        console.log('Lettering.js is required');
        return;
    }

    return this.each(function () {

        if (options) {
            $.extend(settings, options);
        }
        var elem = this,
            delta = (180 / Math.PI),
            txt = elem.innerHTML.replace(/\s/g, ' '),
            letters;

        elem.innerHTML = txt
        $(elem).lettering();

        elem.style.position =  'relative';

        letters = elem.getElementsByTagName('span');

        var layout = function () {
            var tw = 0,
                i,
                offset = 0,
                ch = parseInt($(elem).css('line-height'), 10),
                minRadius,
                origin,
                innerRadius,
                l, style, r, transform,
                bb,
                yMax = Number.MIN_VALUE,
                yMin = Number.MAX_VALUE;

            for (i = 0; i < letters.length; i++) {
                tw += letters[i].offsetWidth;
            }
            minRadius = (tw / Math.PI) / 2 + ch;

            if (settings.fluid) {
                settings.radius = Math.max(elem.offsetWidth / 2, minRadius);
            }
            else if (!settings.radius) {
                settings.radius = minRadius;
            }
            if (settings.dir === -1) {
                origin = 'center ' + (-settings.radius + ch) + 'px';
            } else {
                origin = 'center ' + settings.radius + 'px';
            }

            innerRadius = settings.radius - ch;

            for (i = 0; i < letters.length; i++) {
                l = letters[i];
                offset += l.offsetWidth / 2 / innerRadius * delta;
                l.rot = offset;
                offset += l.offsetWidth / 2 / innerRadius * delta;
            }
            for (i = 0; i < letters.length; i++) {
                l = letters[i]
                style = l.style
                r = (-offset * settings.dir / 2) + l.rot * settings.dir
                transform = 'rotate(' + r + 'deg)';

                style.position = 'absolute';
                style.left = '50%';
                style.marginLeft = -l.offsetWidth / 2 + 'px';

                style.webkitTransform = transform;
                style.MozTransform = transform;
                style.oTransform = transform;
                style.msTransform = transform;
                style.transform = transform;

                style.webkitTransformOrigin = origin;
                style.MozTransformOrigin = origin;
                style.oTransformOrigin = origin;
                style.msTransformOrigin = origin;
                style.transformOrigin = origin;
                if(settings.dir === -1) {
                    style.bottom = 0;
                }

                bb = getPosition(l);
                if (bb.top < yMin) {
                    yMin = bb.top;
                }
                if (bb.top > yMax) {
                    yMax = bb.top;
                }

            }
            elem.style.height = yMax - yMin + ch + 'px';
        };

        var getPosition = function (elem) {
            var docElem = document.documentElement,
                box = elem.getBoundingClientRect();
            return {
                top: box.top + window.pageYOffset - docElem.clientTop,
                left: box.left + window.pageXOffset - docElem.clientLeft
            };
        };

        if (settings.fluid) {
            $(window).resize(function () {
                layout();
            });
        }

        if (document.readyState !== "complete") {
            elem.style.visibility = 'hidden';
            $(window).load(function () {
                elem.style.visibility = 'visible';
                layout();
            });
        } else {
            layout();
        }
    });
};<|endoftext|>


<commit_before>'use strict'

// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++
// DATA SETUP
// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++

// DOM variables
// -----------------
var picContainer = document.getElementById('pic_container');
var left = document.getElementById('left');
var center = document.getElementById('center');
var right = document.getElementById('right');
var clickButton = document.getElementById('clickButton');
var showChart = document.getElementById('chart');

// Global variables
// -----------------
var allProducts = [];
var names = ['bag', 'banana', 'bathroom', 'boots', 'breakfast', 'bubblegum', 'chair', 'cthulhu', 'dog-duck', 'dragon', 'pen', 'pet-sweep', 'scissors', 'shark', 'sweep', 'tauntaun', 'unicorn', 'usb', 'water_can', 'wine_glass'];

// Constructor
// -----------------
function Product(name) {
  this.name = name;
  this.filepath = 'img/' + name + '.jpg';
  this.clicks = 0;
  this.views = 0;
  allProducts.push(this);
}

Product.prototype.render = function () {
  // alert("name: " + this.name + "\nclicks: " + this.clicks + "\nviews: " + this.views );
};

// Instances
// -----------------
for(var i = 0; i < names.length; i++) {
  new Product(names[i]);
}
// console.table(allProducts);

// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++
// DECLARE FUNCTIONS
// (DEFINE ACTIONS)
// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++

function rand() {
  // generate a random number between 0 and allProducts.length
  return Math.floor(Math.random() * allProducts.length);
}

// -----------------------------------------------------------------------------

var newPicsArray = [];
var oldPicsArray = [];
var clickCounter = 0;

// Make a function to prevent duplicates
function makeArrayOfThreeNumbers() {
//set oldPicsArray equals to newPicsArray because we need to prevent each time the code runs it would wipe out the previous array.  The "For" loops (set up later in the code) will helps this run.  This essentially turn new array into old array and so on as it runs through the loop.
  oldPicsArray[0] = newPicsArray[0];
  oldPicsArray[0] = newPicsArray[0];
  oldPicsArray[0] = newPicsArray[0];

// Below is the logic to prevent duplicates with prior set of images (compare newPicsArray with newPicsArray) and within current set of images (compare newPicsArray with oldPicsArray)

  // set initial array to a random set to 3 numbers with indexes [0], [1], [2]
  newPicsArray[0] = rand();
  // while loop to rule out duplicate between new array index [0] and the old("next") array index [0] , [1] and [2]
  while (newPicsArray[0] === oldPicsArray[0] || newPicsArray[0] === oldPicsArray[1] || newPicsArray[0] === oldPicsArray[2]) {
    // console.log(newPicsArray);
    //after it has ruled out that there is no duplicate, set the array index [0] to a random number
    newPicsArray[0] = rand();
  }

  newPicsArray[1] = rand();
  // first: validate within the new array that there's no duplicate between index [0] and [1].  Note: we didnt' have to worry about that for the newPicsArray[0] above because there wasn't any number generated prior to it.
  //second: rule out duplicate between new array index [1] and the old("next") array index [0], [1], [2]
  while (newPicsArray[1] === newPicsArray[0] || newPicsArray[1] === oldPicsArray[0] || newPicsArray[1] === oldPicsArray[1] || newPicsArray[1] === oldPicsArray[2]) {
    //after it has ruled out that there is no duplicate, set the array index [1] to a random number
    newPicsArray[1] = rand();
  }

  newPicsArray[2] = rand();
  //first: validate within the new array that there's no duplicate between index [0] and [1] and [2]. That is index [2] # [0] # [1]
  //second: rule out duplicate between new array index [2] and the old("next") array index [0], [1], [2]
  while (newPicsArray[2] === newPicsArray[0] || newPicsArray[2] === newPicsArray[1] || newPicsArray[2] === oldPicsArray[0] || newPicsArray[2] === oldPicsArray[1] || newPicsArray[2] === oldPicsArray[2]) {
    //after it has ruled out that there is no duplicate, set the array index [2] to a random number
    newPicsArray[2] = rand();
  }

}

// ---------------------------------------------------------------------

// this will place three new images on the page and get view clicks
function showThreePics() {
  makeArrayOfThreeNumbers(); // call function from above

  left.src = allProducts[newPicsArray[0]].filepath;  // this will place first image on the left
  allProducts[newPicsArray[0]].views +=1;  // add 1 to the view of each image clicked on

  center.src = allProducts[newPicsArray[1]].filepath; // this will place second image in the center
  allProducts[newPicsArray[1]].views +=1; // add 1 to the view of each image clicked on

  right.src = allProducts[newPicsArray[2]].filepath; // this will place first image on the right
  allProducts[newPicsArray[2]].views +=1; // add 1 to the view of each image clicked on
}

var clicks = [];
var views = [];

function updateChartArrays() {
  for (var i = 0; i < allProducts.length; i++) {
    clicks[i] = allProducts[i].clicks;
    views[i] = allProducts[i].views;
  }
}

function handleClick(event) {
  if (clickCounter >= 5) {
    // No more clicks should be allowed, we have already hit the total number.
    return;
  }

  // tally the number of clicks
  clickCounter += 1;
  // console.log(clickCounter, 'total clicks so far');

  event.preventDefault();
  // identify who was clicked
  // console.log(event.target.src, 'was clicked');
  // alert for clicks not on images
  if (event.target.id === 'pic_container'){
    return alert ('Please click on a picture, not a background.');
  }

  // tally the click
  if(event.target.id === 'left') {
    allProducts[newPicsArray[0]].clicks +=1;
    // console.log(allProducts[newPicsArray[0]]);
    updateChartArrays();
  }

  if(event.target.id === 'center') {
    allProducts[newPicsArray[1]].clicks +=1;
    // console.log(allProducts[newPicsArray[1]]);
    updateChartArrays();
  }

  if(event.target.id === 'right') {
    allProducts[newPicsArray[2]].clicks +=1;
    // console.log(allProducts[newPicsArray[2]]);
    updateChartArrays();
  }

  // View Result button only appears when total clicks have reached 25 clicks.
  if (clickCounter >= 5) {
    showButton();
  } else {
    showThreePics();
  }
}

// function for button behavior
function showButton() {
  var myButton = document.getElementById('clickButton');
  myButton.style.display = 'block';
}

  // list display of product names and # of views and clicks for each product
function displayList() {
  var picList = document.getElementById('picList');
  picList.innerHTML = '';
  for (var i = 0; i < allProducts.length; i++) {
    var liEl = document.createElement('li');
    var liEl2 = document.createElement('li');
    liEl.textContent = allProducts[i].name + ' has been clicked ' + allProducts[i].clicks + ' times';
    liEl2.textContent = allProducts[i].name + ' has been viewed ' + allProducts[i].views + ' times';
    picList.appendChild(liEl);
    picList.appendChild(liEl2);
  }
  drawChart();
}

// emptying the chart and update values we put into it
function displayChart() {
  var chart = document.getElementsByClassName('chart');
  chart.innerHTML = '';
  updateChartArrays();
}

// CHART -----------------------------------------------------------------------------------------------------------------

//declare global variables for chart
var tallyChart;
var chartDrawn = false;

// ++++++++++++++++++++++++++++++++++++++++++++
// CHART STUFF
// ++++++++++++++++++++++++++++++++++++++++++++

var data = {
  labels: names,  // product names array we declared earlier
  datasets: [
    {
      label: 'Clicks',  //clicks array we declared earlier
      data: clicks,
      backgroundColor:
        'blue',
      hoverBackgroundColor:
        'lightblue'
    },

    {
      label: 'Views',  //clicks array we declared earlier
      data: views,
      backgroundColor:
        'blue',
      hoverBackgroundColor:
        'lightblue'
    }]
};

function drawChart() {
  var ctx = document.getElementById('click-chart').getContext('2d'); // needs to attach to canvas id in html
  tallyChart = new Chart(ctx,{
    type: 'bar',
    data: data,
    options: {
      responsive: false
    },
    scales: [{
      ticks: {
        beginAtZero: true
      }
    }]
  });
  chartDrawn = true;
}

// ++++++++++++++++++++++++++++++++++++++++++++
// EVENT LISTENERS
// ++++++++++++++++++++++++++++++++++++++++++++

document.getElementById('click-chart').addEventListener('click', function(){
  drawChart();
});

if (chartDrawn) {
  tallyChart.update();
}

// display 3 new images
showThreePics();
clickButton.addEventListener('click', displayList);
picContainer.addEventListener('click', handleClick);
showChart.addEventListener('click', showChart);
<commit_msg>working on Local Storage<commit_after>'use strict'

// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++
// DATA SETUP
// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++

// DOM variables
// -----------------
var picContainer = document.getElementById('pic_container');
var left = document.getElementById('left');
var center = document.getElementById('center');
var right = document.getElementById('right');
var clickButton = document.getElementById('clickButton');
var showChart = document.getElementById('chart');
var storedData = JSON.stringify(allProducts); // convert and assign all products into string
var retrievedData;

// Global variables
// -----------------
var allProducts = [];
var names = ['bag', 'banana', 'bathroom', 'boots', 'breakfast', 'bubblegum', 'chair', 'cthulhu', 'dog-duck', 'dragon', 'pen', 'pet-sweep', 'scissors', 'shark', 'sweep', 'tauntaun', 'unicorn', 'usb', 'water_can', 'wine_glass'];

//before pageload, use IF statement to validate that local storage exists. If LS exists, then retrieve data, convert data, assign data
if(storedData !== '') {
  retrievedData = localStorage.getItem(storedData);
  JSON.parse(storedData);
  localStorage.setItem(allProducts)
}
else {

}
// -----------------------------------------------------------------------------
// Constructor
function Product(name) {
  this.name = name;
  this.filepath = 'img/' + name + '.jpg';
  this.clicks = 0;
  this.views = 0;
  allProducts.push(this);
}

Product.prototype.render = function () {
  // alert("name: " + this.name + "\nclicks: " + this.clicks + "\nviews: " + this.views );
};

// Instances
// -----------------
for(var i = 0; i < names.length; i++) {
  new Product(names[i]);
}
// console.table(allProducts);

// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++
// DECLARE FUNCTIONS
// (DEFINE ACTIONS)
// ++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++

function rand() {
  // generate a random number between 0 and allProducts.length
  return Math.floor(Math.random() * allProducts.length);
}

// -----------------------------------------------------------------------------

var newPicsArray = [];
var oldPicsArray = [];
var clickCounter = 0;

// Make a function to prevent duplicates
function makeArrayOfThreeNumbers() {
//set oldPicsArray equals to newPicsArray because we need to prevent each time the code runs it would wipe out the previous array.  The "For" loops (set up later in the code) will helps this run.  This essentially turn new array into old array and so on as it runs through the loop.
  oldPicsArray[0] = newPicsArray[0];
  oldPicsArray[0] = newPicsArray[0];
  oldPicsArray[0] = newPicsArray[0];

// Below is the logic to prevent duplicates with prior set of images (compare newPicsArray with newPicsArray) and within current set of images (compare newPicsArray with oldPicsArray)

  // set initial array to a random set to 3 numbers with indexes [0], [1], [2]
  newPicsArray[0] = rand();
  // while loop to rule out duplicate between new array index [0] and the old("next") array index [0] , [1] and [2]
  while (newPicsArray[0] === oldPicsArray[0] || newPicsArray[0] === oldPicsArray[1] || newPicsArray[0] === oldPicsArray[2]) {
    // console.log(newPicsArray);
    //after it has ruled out that there is no duplicate, set the array index [0] to a random number
    newPicsArray[0] = rand();
  }

  newPicsArray[1] = rand();
  // first: validate within the new array that there's no duplicate between index [0] and [1].  Note: we didnt' have to worry about that for the newPicsArray[0] above because there wasn't any number generated prior to it.
  //second: rule out duplicate between new array index [1] and the old("next") array index [0], [1], [2]
  while (newPicsArray[1] === newPicsArray[0] || newPicsArray[1] === oldPicsArray[0] || newPicsArray[1] === oldPicsArray[1] || newPicsArray[1] === oldPicsArray[2]) {
    //after it has ruled out that there is no duplicate, set the array index [1] to a random number
    newPicsArray[1] = rand();
  }

  newPicsArray[2] = rand();
  //first: validate within the new array that there's no duplicate between index [0] and [1] and [2]. That is index [2] # [0] # [1]
  //second: rule out duplicate between new array index [2] and the old("next") array index [0], [1], [2]
  while (newPicsArray[2] === newPicsArray[0] || newPicsArray[2] === newPicsArray[1] || newPicsArray[2] === oldPicsArray[0] || newPicsArray[2] === oldPicsArray[1] || newPicsArray[2] === oldPicsArray[2]) {
    //after it has ruled out that there is no duplicate, set the array index [2] to a random number
    newPicsArray[2] = rand();
  }

}

// ---------------------------------------------------------------------

// this will place three new images on the page and get view clicks
function showThreePics() {
  makeArrayOfThreeNumbers(); // call function from above

  left.src = allProducts[newPicsArray[0]].filepath;  // this will place first image on the left
  allProducts[newPicsArray[0]].views +=1;  // add 1 to the view of each image clicked on

  center.src = allProducts[newPicsArray[1]].filepath; // this will place second image in the center
  allProducts[newPicsArray[1]].views +=1; // add 1 to the view of each image clicked on

  right.src = allProducts[newPicsArray[2]].filepath; // this will place first image on the right
  allProducts[newPicsArray[2]].views +=1; // add 1 to the view of each image clicked on
}

var clicks = [];
var views = [];

function updateChartArrays() {
  for (var i = 0; i < allProducts.length; i++) {
    clicks[i] = allProducts[i].clicks;
    views[i] = allProducts[i].views;
  }
}

function handleClick(event) {
  if (clickCounter >= 5) {
    // No more clicks should be allowed, we have already hit the total number.
    return;
  }

  // tally the number of clicks
  clickCounter += 1;
  // console.log(clickCounter, 'total clicks so far');

  event.preventDefault();
  // identify who was clicked
  // console.log(event.target.src, 'was clicked');
  // alert for clicks not on images
  if (event.target.id === 'pic_container'){
    return alert ('Please click on a picture, not a background.');
  }

  // tally the click
  if(event.target.id === 'left') {
    allProducts[newPicsArray[0]].clicks +=1;
    // console.log(allProducts[newPicsArray[0]]);
    updateChartArrays();
  }

  if(event.target.id === 'center') {
    allProducts[newPicsArray[1]].clicks +=1;
    // console.log(allProducts[newPicsArray[1]]);
    updateChartArrays();
  }

  if(event.target.id === 'right') {
    allProducts[newPicsArray[2]].clicks +=1;
    // console.log(allProducts[newPicsArray[2]]);
    updateChartArrays();
  }

  // View Result button only appears when total clicks have reached 25 clicks.
  if (clickCounter >= 5) {
    showButton();
  } else {
    showThreePics();
  }
}

// function for button behavior
function showButton() {
  var myButton = document.getElementById('clickButton');
  myButton.style.display = 'block';
}

  // list display of product names and # of views and clicks for each product
function displayList() {
  var picList = document.getElementById('picList');
  picList.innerHTML = '';
  for (var i = 0; i < allProducts.length; i++) {
    var liEl = document.createElement('li');
    var liEl2 = document.createElement('li');
    liEl.textContent = allProducts[i].name + ' has been clicked ' + allProducts[i].clicks + ' times';
    liEl2.textContent = allProducts[i].name + ' has been viewed ' + allProducts[i].views + ' times';
    picList.appendChild(liEl);
    picList.appendChild(liEl2);
  }
  drawChart();
}

// emptying the chart and update values we put into it
function displayChart() {
  var chart = document.getElementsByClassName('chart');
  chart.innerHTML = '';
  updateChartArrays();
}

// CHART -----------------------------------------------------------------------------------------------------------------

//declare global variables for chart
var tallyChart;
var chartDrawn = false;

// ++++++++++++++++++++++++++++++++++++++++++++
// CHART STUFF
// ++++++++++++++++++++++++++++++++++++++++++++

var data = {
  labels: names,  // product names array we declared earlier
  datasets: [
    {
      label: 'Clicks',  //clicks array we declared earlier
      data: clicks,
      backgroundColor:
        'blue',
      hoverBackgroundColor:
        'lightblue'
    },

    {
      label: 'Views',  //clicks array we declared earlier
      data: views,
      backgroundColor:
        'blue',
      hoverBackgroundColor:
        'lightblue'
    }]
};

function drawChart() {
  var ctx = document.getElementById('click-chart').getContext('2d'); // needs to attach to canvas id in html
  tallyChart = new Chart(ctx,{
    type: 'bar',
    data: data,
    options: {
      responsive: false
    },
    scales: [{
      ticks: {
        beginAtZero: true
      }
    }]
  });
  chartDrawn = true;
}

// ++++++++++++++++++++++++++++++++++++++++++++
// EVENT LISTENERS
// ++++++++++++++++++++++++++++++++++++++++++++

document.getElementById('click-chart').addEventListener('click', function(){
  drawChart();
});

if (chartDrawn) {
  tallyChart.update();
}

// display 3 new images
showThreePics();
clickButton.addEventListener('click', displayList);
picContainer.addEventListener('click', handleClick);
showChart.addEventListener('click', showChart);
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Switch } from 'react-router-dom'
import PropTypes from 'prop-types'
import Loadable from '@7rulnik/react-loadable'
import Loading from './components/Loading/Loading'
import AuthActionCreator from './actions/auth'
import ForgotPasswordForm from './components/ForgotPasswordForm/ForgotPasswordForm'
import Header from './components/Header/Header'
import Home from './components/Home/Home'
import About from './components/About/About'
import LoginForm from './components/LoginForm/LoginForm'
import restricted from './components/Restricted/Restricted'
import SignupForm from './components/SignupForm/SignupForm'
import Version from './components/Version/Version'
import LandingA from './components/LandingA/LandingA'
import LandingC from './components/LandingC/LandingC'
import ProjectDetail from './components/Project/ProjectDetail'
import Footer from './components/LandingF/LandingF'
import LandingB from './components/LandingB/LandingB'
import LandingE from './components/LandingE/LandingE'
import LandingD from './components/LandingD/LandingD'

// set up components for lazy loading
const ProfileRestrictedLoadable = restricted(Loadable({
  loader: () => import('./components/Profile/Profile'),
  loading: Loading
}))

const CreateProjectRestrictedLoadable = restricted(Loadable({
  loader: () => import('./components/CreateProjectForm/CreateProjectForm'),
  loading: Loading
}))

const ApplicationsRestrictedLoadable = restricted(Loadable({
  loader: () => import('./components/Applications/ApplicationsList'),
  loading: Loading
}))

class App extends Component {
  componentDidMount () {
    this.props.appLoad()
  }

  renderAppLoaded () {
    return (

        <Version />
        <Switch>
          <Route exact path='/' component={Home}/>
          <Route path='/about' component={About}/>
          <Route path='/create-project' component={CreateProjectRestrictedLoadable}/>
          <Route path='/forgot-password' component={ForgotPasswordForm}/>
          <Route path='/login' component={LoginForm}/>
          <Route path='/profile' component={ProfileRestrictedLoadable}/>
          <Route path='/signup' component={SignupForm}/>
          <Route path='/landing-a' component={LandingA} />
          <Route path='/landing-c' component={LandingC} />
          <Route path='/landing-f' component={Footer} />
          <Route path='/landing-b' component={LandingB} />
          <Route path='/landing-f' component={Footer} />
          <Route path='/landing-e' component={LandingE} />
          <Route path='/landing-d' component={LandingD} />
          <Route path='/show-applications' component={ApplicationsRestrictedLoadable} />
          <Route path='/projects/:id' component={ProjectDetail} />
        </Switch>

    )
  }

  renderAppNotLoaded () {
    return (

    )
  }

  render () {
    return this.props.appLoaded
      ? this.renderAppLoaded()
      : this.renderAppNotLoaded()
  }
}

function mapStateToProps (state) {
  return {
    appLoaded: state.common.appLoaded
  }
}

const mapDispatchToProps = {
  appLoad: AuthActionCreator.appLoad
}

App.propTypes = {
  appLoad: PropTypes.func,
  appLoaded: PropTypes.bool
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
<commit_msg> some changes in app.js<commit_after>import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Switch } from 'react-router-dom'
import PropTypes from 'prop-types'
import Loadable from '@7rulnik/react-loadable'
import Loading from './components/Loading/Loading'
import AuthActionCreator from './actions/auth'
import ForgotPasswordForm from './components/ForgotPasswordForm/ForgotPasswordForm'
import Header from './components/Header/Header'
import Home from './components/Home/Home'
import About from './components/About/About'
import LoginForm from './components/LoginForm/LoginForm'
import restricted from './components/Restricted/Restricted'
import SignupForm from './components/SignupForm/SignupForm'
import Version from './components/Version/Version'
import LandingA from './components/LandingA/LandingA'
import LandingC from './components/LandingC/LandingC'
<<<<<<>>>>>>  some changes in app.js

// set up components for lazy loading
const ProfileRestrictedLoadable = restricted(Loadable({
  loader: () => import('./components/Profile/Profile'),
  loading: Loading
}))

const CreateProjectRestrictedLoadable = restricted(Loadable({
  loader: () => import('./components/CreateProjectForm/CreateProjectForm'),
  loading: Loading
}))

const ApplicationsRestrictedLoadable = restricted(Loadable({
  loader: () => import('./components/Applications/ApplicationsList'),
  loading: Loading
}))

class App extends Component {
  componentDidMount () {
    this.props.appLoad()
  }

  renderAppLoaded () {
    return (

        <Version />
        <Switch>
          <Route exact path='/' component={Home}/>
          <Route path='/about' component={About}/>
          <Route path='/create-project' component={CreateProjectRestrictedLoadable}/>
          <Route path='/forgot-password' component={ForgotPasswordForm}/>
          <Route path='/login' component={LoginForm}/>
          <Route path='/profile' component={ProfileRestrictedLoadable}/>
          <Route path='/signup' component={SignupForm}/>
          <Route path='/landing-a' component={LandingA} />
          <Route path='/landing-c' component={LandingC} />
<<<<<<< HEAD
          <Route path='/landing-f' component={Footer} />
          <Route path='/landing-b' component={LandingB} />
          <Route path='/landing-f' component={Footer} />
          <Route path='/landing-e' component={LandingE} />
          <Route path='/landing-d' component={LandingD} />
=======
          <Route path='/landing-f' component={LandingF} />
>>>>>>>  some changes in app.js
          <Route path='/show-applications' component={ApplicationsRestrictedLoadable} />
          <Route path='/projects/:id' component={ProjectDetail} />
        </Switch>

    )
  }

  renderAppNotLoaded () {
    return (

    )
  }

  render () {
    return this.props.appLoaded
      ? this.renderAppLoaded()
      : this.renderAppNotLoaded()
  }
}

function mapStateToProps (state) {
  return {
    appLoaded: state.common.appLoaded
  }
}

const mapDispatchToProps = {
  appLoad: AuthActionCreator.appLoad
}

App.propTypes = {
  appLoad: PropTypes.func,
  appLoaded: PropTypes.bool
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
<|endoftext|>


<commit_before>const client = require('prom-client');

client.collectDefaultMetrics();

const thermometer = new client.Gauge({
  name: 'pigarage_thermometer',
  help: 'Thermometer',
  labelNames: ['location'],
});

const opening = new client.Gauge({
  name: 'pigarage_opening',
  help: 'Door, window state (1 - closed, 0 - open)',
  labelNames: ['location'],
});

const clickCounter = new client.Counter({
  name: 'pigarage_click_counter',
  help: 'Click counter',
  labelNames: ['location'],
});

module.exports = (app) => {
  app.on('devices:cpu-temperature', value => thermometer.set({ location: 'cpu' }, value));
  app.on('devices:garage-temperature', value => thermometer.set({ location: 'garage' }, value));
  app.on('devices:garage-gate', value => opening.set({ location: 'garage_gate' }, value));
  app.on('devices:garage-gate:click', () => clickCounter.labels('location', 'garage_gate').inc());

  app.get('/metrics', (req, res) => {
    res.set('Content-Type', client.register.contentType);
    res.end(client.register.metrics());
  });
};
<commit_msg>Fix click counter<commit_after>const client = require('prom-client');

client.collectDefaultMetrics();

const thermometer = new client.Gauge({
  name: 'pigarage_thermometer',
  help: 'Thermometer',
  labelNames: ['location'],
});

const opening = new client.Gauge({
  name: 'pigarage_opening',
  help: 'Door, window state (1 - closed, 0 - open)',
  labelNames: ['location'],
});

const clickCounter = new client.Counter({
  name: 'pigarage_click_counter',
  help: 'Click counter',
  labelNames: ['location'],
});

module.exports = (app) => {
  app.on('devices:cpu-temperature', value => thermometer.set({ location: 'cpu' }, value));
  app.on('devices:garage-temperature', value => thermometer.set({ location: 'garage' }, value));
  app.on('devices:garage-gate', value => opening.set({ location: 'garage_gate' }, value));
  app.on('devices:garage-gate:click', () => clickCounter.inc({ location: 'garage_gate' }));

  app.get('/metrics', (req, res) => {
    res.set('Content-Type', client.register.contentType);
    res.end(client.register.metrics());
  });
};
<|endoftext|>


<commit_before>const cme = chrome.management;
const getI18N = chrome.i18n.getMessage;

// Generate page
const $searchField = $(`<input placeholder="${ getI18N('searchTxt') }">`);
const eul = $('
');
const $options = $('
');
const $disableAllButton = $(`<button>${ getI18N('disAll') }</button>`);
const $extensionPageButton = $(`<button>${ getI18N('extensionPage') }</button>`);

$options
	.append($disableAllButton)
	.append($extensionPageButton);

$('body')
	.append($searchField)
	.append($options)
	.append(eul);

$searchField.focus();
window.scrollTo(0, 0); // fix overscroll caused by autofocus

// Generate extension list
cme.getAll(ets => {
	const listHTML = ets
	.filter(extension => extension.id !== 'pbgjpgbpljobkekbhnnmlikbbfhbhmem')
	.sort((a, b) => {
		if (a.enabled === b.enabled) {
			return a.name.localeCompare(b.name); // sort by name
		}
		return a.enabled  {
	const $extension = $(e.currentTarget).parent();
	const id = $extension.attr('id');
	const wasEnabled = !$extension.hasClass('disabled');

	cme.setEnabled(id, !wasEnabled, () => {
		$extension.toggleClass('disabled', wasEnabled);
		if (wasEnabled) {
			eul.append($extension);
		} else {
			eul.prepend($extension);
		}
	})
}).on('mouseup', '.extName', e => {
	if (e.which == 3) {
		cme.uninstall(e.target.dataset.id);
	}
});

cme.onUninstalled.addListener(id => {
	$(`#${id}`).remove();
});

const extensions = $('#extList li');
$searchField.on('input', function () {
	const keywords = this.value.split(' ').filter(s => s.length);
	const hiddenExtensions = extensions.not((i, el) => {
		return keywords.every(word => el.dataset.name.includes(word));
	});
	hiddenExtensions.hide();
	extensions.not(hiddenExtensions).show();
});

// disable the default context menu
eul.on('contextmenu', () => false);

$disableAllButton.click(() => {
	if (confirm(getI18N('disableAll'))) {
		disableAll();
	}
});

$extensionPageButton.click(() => {
	chrome.tabs.create({url: 'chrome://extensions'});
});

/**
 * FUNCTIONS
 */
function getIcon(icons, size = 16) {
	// Set fallback icon
	let selectedIcon = chrome.extension.getURL('icon-puzzle.svg');

	// Get retina size if necessary
	size *= window.devicePixelRatio;

	if (icons && icons.length) {
		// Get a large icon closest to the desired size
		icons.reverse().some(icon => {
			if (icon.size < size) {
				return false;
			}
			selectedIcon = icon.url;
		});
	}
	return selectedIcon;
}

function createList(e) {
	return `

				${e.name}

			${
				e.optionsUrl ? `

				` : ``
			}

	`;
}

function disableAll() {
	cme.getAll(ets => {
		const myid = getI18N('@@extension_id');
		ets.forEach(extension => {
			if(extension.enabled && extension.id !== myid) {
				cme.setEnabled(extension.id, false);
			}
		});
		$('.ext').addClass('disabled');
	});
}
<commit_msg>Restore uninstall feature<commit_after>const cme = chrome.management;
const getI18N = chrome.i18n.getMessage;

// Generate page
const $searchField = $(`<input placeholder="${ getI18N('searchTxt') }">`);
const eul = $('
');
const $options = $('
');
const $disableAllButton = $(`<button>${ getI18N('disAll') }</button>`);
const $extensionPageButton = $(`<button>${ getI18N('extensionPage') }</button>`);

$options
	.append($disableAllButton)
	.append($extensionPageButton);

$('body')
	.append($searchField)
	.append($options)
	.append(eul);

$searchField.focus();
window.scrollTo(0, 0); // fix overscroll caused by autofocus

// Generate extension list
cme.getAll(ets => {
	const listHTML = ets
	.filter(extension => extension.id !== 'pbgjpgbpljobkekbhnnmlikbbfhbhmem')
	.sort((a, b) => {
		if (a.enabled === b.enabled) {
			return a.name.localeCompare(b.name); // sort by name
		}
		return a.enabled  {
	const $extension = $(e.currentTarget).parent();
	const id = $extension.attr('id');
	const wasEnabled = !$extension.hasClass('disabled');

	cme.setEnabled(id, !wasEnabled, () => {
		$extension.toggleClass('disabled', wasEnabled);
		if (wasEnabled) {
			eul.append($extension);
		} else {
			eul.prepend($extension);
		}
	})
}).on('mouseup', '.ext', e => {
	if (e.which == 3) {
		cme.uninstall(e.currentTarget.id);
	}
});

cme.onUninstalled.addListener(id => {
	$(`#${id}`).remove();
});

const extensions = $('#extList li');
$searchField.on('input', function () {
	const keywords = this.value.split(' ').filter(s => s.length);
	const hiddenExtensions = extensions.not((i, el) => {
		return keywords.every(word => el.dataset.name.includes(word));
	});
	hiddenExtensions.hide();
	extensions.not(hiddenExtensions).show();
});

// disable the default context menu
eul.on('contextmenu', () => false);

$disableAllButton.click(() => {
	if (confirm(getI18N('disableAll'))) {
		disableAll();
	}
});

$extensionPageButton.click(() => {
	chrome.tabs.create({url: 'chrome://extensions'});
});

/**
 * FUNCTIONS
 */
function getIcon(icons, size = 16) {
	// Set fallback icon
	let selectedIcon = chrome.extension.getURL('icon-puzzle.svg');

	// Get retina size if necessary
	size *= window.devicePixelRatio;

	if (icons && icons.length) {
		// Get a large icon closest to the desired size
		icons.reverse().some(icon => {
			if (icon.size < size) {
				return false;
			}
			selectedIcon = icon.url;
		});
	}
	return selectedIcon;
}

function createList(e) {
	return `

				${e.name}

			${
				e.optionsUrl ? `

				` : ``
			}

	`;
}

function disableAll() {
	cme.getAll(ets => {
		const myid = getI18N('@@extension_id');
		ets.forEach(extension => {
			if(extension.enabled && extension.id !== myid) {
				cme.setEnabled(extension.id, false);
			}
		});
		$('.ext').addClass('disabled');
	});
}
<|endoftext|>