Gas Prices Data Visualization

by Admiral Potato

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.js"></script>
<script src="http://threejs.org/examples/fonts/helvetiker_regular.typeface.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">


<div id="rootContainer" class="container-fluid">
	<div id="rootRow" class="row">
		<div class="col-xs-2">
			<div class="panel">
				<div class="btn-group-vertical" role="group">
					<button type="button" class="btn btn-default">Bob</button>
					<button type="button" class="btn btn-default">Joe</button>
					<button type="button" class="btn btn-default">Sally</button>
					<button type="button" class="btn btn-default">Lill</button>
				</div>
			</div>
		</div>
		<div id="rootCanvasHolder" class="col-xs-10">
			<div id="canvasHolder"></div>
		</div>
	</div>
</div>

<div id="data">
<div id="data-receipts" data-source="Admiral's gas receipt collection">
date	time	businessName	address	city	zipCode	lat	lng	pricePerGal	volume	total	mileage
8/1/2012	10:42	Smiths 133	845 E 4500 S	SLC	84107	40.675392	-111.867491	3.399	12.798	43.5	91759
8/17/2012	16:05	Costco 113	1818 S 300 W	SLC	84115	40.730608	-111.902929	3.359	16.968	57	92119
9/13/2012	20:32	Smiths 133	845 E 4500 S	SLC	84107	40.675392	-111.867491	3.299	16.069	53.01	92786
9/18/2013	0:35	Nolan's Chevron	210 S 700 E	SLC	84102	40.764568	-111.871518	3.689	14.028	51.75	103411
12/2/2012	18:43	Smiths 133	845 E 4500 S	SLC	84107	40.675392	-111.867491	1.439	13.201	19	94608
3/17/2013	17:22	Holiday Oil #47	309 W Main St	American Fork	84003	40.376708	-111.807044	3.279	9.76	32	97671
9/5/2013	23:06	Extramart #90	2110 S 1300...

CSS

body{
	padding: 0;
	margin: 0;
}

#data{
	display: none;
}
#canvasHolder{
	height: 100%;
	background-color: #f00;
}

html,
body,
#rootContainer,
#rootRow,
#rootCanvasHolder,
#canvasHolder{
	height: 100%;
}

#rootCanvasHolder{
	padding: 0;
}

canvas{
	display: block;
	width: 100%;
	height: 100%;
}

JavaScript

//data prep parts
var prepAsString = function (record, label, string) {
		record[label] = string;
	},
	prepAsNumber = function (record, label, string) {
		record[label] = parseFloat(string);
	},
	dataPrepMethodMap = {
		"date": function (record, label, string) {
			record[label] = new Date(string);
		},
		"time": function (record, label, string) {
			var split = string.split(':');
			record['date'].setHours(split[0]);
			record['date'].setMinutes(split[1]);
		},
		"businessName": prepAsString,
		"address": prepAsString,
		"city": prepAsString,
		"state": prepAsString,
		"abbrev": prepAsString,
		"label": prepAsString,
		"zipCode": prepAsNumber,
		"lat": prepAsNumber,
		"lng": prepAsNumber,
		"pricePerGal": prepAsNumber,
		"volume": prepAsNumber,
		"total": prepAsNumber,
		"mileage": prepAsNumber,
		"hue": prepAsNumber,
		"bordindx": prepAsNumber,
		"st1st2": prepAsString,
		"milemark": prepAsString,
		"st1": prepAsString,
		"st2": prepAsString
	},
	prepDataByTypeName = function (dataTypeName) {
		var result = [],
			split = dataText = document.getElementById('data-' + dataTypeName).innerText.trim().split('\n'),
			labels = split.shift().split('\t');
		split.forEach(function (rowString) {
			var row = rowString.split('\t'),
				record = {
					type: dataTypeName
				};
			row.forEach(function (value, index) {
				var label = labels[index];
				dataPrepMethodMap[label](record, label, value);
			});
			result.push(record);
		});
		return result;
	},
	dataTypes = 'receipts,orientation,borders,capitals'.split(','),
	getDataByTypes =  function(){
		var result = {};
		dataTypes.forEach(function(typeName){
			result[typeName] = prepDataByTypeName(typeName);
		});
		return result;
	},
	data = getDataByTypes(),
	lerp = function (a, b, t) {
		return a + ((b - a) * t);
	},
	getDataLatLngBoundingBox = function (dataSet) {
		var cloneSet = dataSet.slice(),//because we're operating on it
			firstResult = cloneSet.shift(),
			result = {
				latMin: firstResult.lat,
				latMax:...