<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="[Trail Traffic Counter Analysis and Reporting]">
<title>File Upload, Analysis and Reporting</title>
<!-- Styles -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css">
<link href="assets/js/google-code-prettify/prettify.css" rel="stylesheet">
<link href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.min.css" rel="stylesheet" type="text/css" />
<!-- JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1.1','packages':['table','corechart', 'bar']}]}"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="http://google-code-prettify.googlecode.com/svn/trunk/src/prettify.js"></script>
</head>
<body>
<div class="container">
<div id="content">
<!-- Set up the Navigation Tabs -->
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#select" data-toggle="tab">Start Here</a> </li>
<li><a href="#preview" data-toggle="tab">Preview</a> </li>
<li><a href="#refine" data-toggle="tab">Refine</a> </li>
<li><a href="#report" data-toggle="tab">Report</a> </li>
<li><a href="#help" data-toggle="tab">Help</a> </li>
</ul>
<div id="my-tab-content" class="tab-content">
<!-- Select Tab -->
<div class="tab-pane active" id="select">
<br/>
<p>Trail Traffic Counter Analysis and Reporting Tool.</p>
<form class="form-horizontal">
<div class="form-group">
<div class="col-lg-6 col-sm-6 col-12">
<p>Select The File You Wish To Analyze and Timezone for sensor's clock</p>
<div class="input-group">
<span class="input-group-btn">
<span class="btn btn-primary btn-file">
Browse… <input type="file" id=files multiple="false">
</span>
</span>
<input type="text" class="form-control" readonly>
</div>
<span class="help-block">
Your data is private. Nothing is uploaded or saved in this process.
</span>
<span>
<form role="form">
<label class="radio-inline" id="timezone">
<input type="radio" autocomplete="off" name="optradio" value="1" checked>UTC Time
</label>
<label class="radio-inline">
<input type="radio" autocomplete="off" name="optradio" value="0">Local Time
</label>
</form>
</span>
</div>
</div>
<button id=upload type="button" class="btn btn-primary">Start</button>
</form>
</div>
<!-- Preview Tab -->
<div class="tab-pane" id="preview">
<h4> Header information:</h4>
<div class="row">
<div class="col-md-6">
<p id="header0">This tab will let you preview the file contents once loaded.</p>
<p id="header1"></p>
<p id="header2"></p>
<p id="header3"></p>
<p id="header4"></p>
<h4> Recorded Events:</h4>
<div class="datapoints"></div>
</div>
<div class="drop-dialog">
<div class="col-md-6">
<p>Trim the file below then click on the "Refine" tab to continue</p>
<div class="form-group">
<label for="topDrop">Number of Rows to Drop from the Top:</label>
<select class="form-control" id="topDrop">
<option>0</option>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
</select>
<label for="bottomDrop">Number of Rows to Drop from the Bottom:</label>
<select class="form-control" id="bottomDrop">
<option>0</option>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Refine Tab -->
<div class="tab-pane" id="refine">
<p id="startTime">This tab will help you clean up the data once it is populated.</p>
<p id="endTime"></p>
<form class="form-inline">
<div class="form-group">
<label for="cityInput">City</label>
<input type="text" class="form-control" id="cityInput" placeholder="City Name">
</div>
<div class="form-group">
<label for="stInput">State</label>
<select class="form-control" id="stInput">
<option>NC</option>
<option>SC</option>
<option>VA</option>
<option>KY</option>
</select>
</div>
<button type="button" id="weatherButton" class="btn btn-primary">Get Weather</button>
</form>
<br/>
<div id="table_div"></div>
<br/>
<div class="form-buttons">
<p> Select the level of filtering </p>
<form role="form">
<label class="radio-inline">
<input type="radio" autocomplete="off" id="noFilter" name="optradio" checked>None
</label>
<label class="radio-inline">
<input type="radio" autocomplete="off" id="modFilter" name="optradio">Moderate
</label>
<label class="radio-inline">
<input type="radio" autocomplete="off" id="highFilter" name="optradio">High
</label>
</form>
</div>
</div>
<!-- Report Tab -->
<div class="tab-pane" id="report">
<h4>Trail Traffic Report</h4>
<p id="reportSubtitle">This is where the final report goes.</p>
<div id="chart_div"></div>
<br/>
<div id="table2_div"></div>
<br/>
<div class="row">
<div class="col-md-6">
<div class="textarea">
<form role="form">
<div class="form">
<label for="comment">Comment:</label>
<textarea class="form-control" rows="5" id="comment"></textarea>
</div>
</form>
</div>
</div>
<div class="col-md-6"></div>
</div>
</div>
<!-- Help Tab -->
<div class="tab-pane" id="help">
<h4>Help</h4>
<p> Send me a note describing your question, I will get back to you within 12 hours with an answer. </p>
<a class="btn btn-primary" type="button" href="mailto:chip@seeinsights.com?Subject=Help%20Request">Send Mail</a>
</div>
</div>
</div>
</div>
</body>
</html>
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
.bold-border {
border: 3px solid black;
}
input[readonly] {
background-color: white !important;
cursor: text !important;
}
// File and Program Variables
var fileInput = $('#files');
var uploadButton = $('#upload');
var refreshFlag = true; // Keeps track of whether we need to refresh results
var datapoints = []; // Array where the data points are stored (no header)
var filteredDatapoints = []; // This is where the filtered data points will go
var url = "http://api.wunderground.com/api/d527f77e6c685457/"; // Weather Underground URL with my API key
var daysCovered = []; // Index of unique days covered
var daysCoveredIndex = 0; // Keeps track of where we are in the index of days covered
var weatherData = []; // Stores the weather for the daysCovered
var delayValue = 0; // What is the value of the delay for the data set
var data = new google.visualization.DataTable(); // Data table and column headers for reporting
data.addColumn('string', 'Day');
data.addColumn('number', 'Morning');
data.addColumn('number', 'Afternoon');
data.addColumn('number', 'Evening');
data.addColumn('number', 'Night');
data.addColumn('number', 'Total');
data.addColumn('string', 'Weather');
var chartdata = new google.visualization.DataTable(); // Data table and column headers for chart
chartdata.addColumn('string', 'Day');
chartdata.addColumn('number', 'Morning');
chartdata.addColumn('number', 'Afternoon');
chartdata.addColumn('number', 'Evening');
chartdata.addColumn('number', 'Night');
// We will be using the Google Visualization API for table and charts: https://developers.google.com/chart/
var table = new google.visualization.Table(document.getElementById('table_div'));
var table2 = new google.visualization.Table(document.getElementById('table2_div'));
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
// Event handler for the start function
uploadButton.on('click', function() {
refreshFlag = true;
if (!window.FileReader) {
alert('Your browser is not supported');
}
var input = fileInput.get(0);
// Create a reader object
var reader = new FileReader();
if (input.files.length) {
var textFile = input.files[0];
reader.readAsText(textFile);
$(reader).on('load', processFile); // File uploaded
$('h4').show(); // Now we can show the interface elements
$(".form-inline, .form, .form-buttons, .drop-dialog").show();
$('#table_div, #chart_div,#table2_div').show();
}
else {
alert('Please choose a file and press "Start" before continuing');
}
});
// This is the function that processes the file we upload and provides the preview
function processFile(e) {
datapoints = []; // clear the datapoints array
data.removeRows(0,data.getNumberOfRows()); // Clear the data visualzation tables
chartdata.removeRows(0,chartdata.getNumberOfRows());
var file = e.target.result, results;
var fileOffset = 0;
var tz = "";
var c = "";
if ($('#timezone input:radio:checked').val()) {
tz=" UTC";
}
if (file && file.length) {
results = file.split("\n");
// Here is where we will find the right offset for the last dataset (if multiple sets in file)
for (i=0; i< results.length; i++) {
c = results[i];
if (c.substr(0,5) === "Trail") {
fileOffset = i;
}
}
console.log("fileOffset =" + fileOffset); // Debug line to show where the start of the last data set resides
// Now we will have to use the fileOffset to report from the last set and create the datapoints array
$('#tabs a[href="#preview"]').tab('show'); // Move the focus to the Preview Tab
$('#header0').html(results[0+fileOffset]);
$('#header1').html(results[1+fileOffset]);
$('#header2').html(results[2+fileOffset]);
// Get the Delay value while we are here
c = results[3+fileOffset];
$('#header3').html(c);
var pos = c.indexOf("=")+1;
var len = c.length;
delayValue = Number(c.substr(pos,(len-pos)));
$('#header4').html(results[4+fileOffset]);
for (i=(5+fileOffset); i<results.length;i++) {
$('.datapoints').append(results[i]+"<br>");
c = results[i];
// Extract Time and Date information by parsing the string. Format is: 06/15/14 19:30:59 UTC
// datapoints[i-5-fileOffset] = new Date(c.substr(0,6)+"20"+c.substr(6,2)+" "+c.substr(9,8)+" UTC");
datapoints[i-5-fileOffset] = new Date(c + tz);
}
}
results = []; // Clear the array as we are now done with it.
}
// Call to draw the table once Google visualization is loaded
function drawTable() {
// Refine Table Options
var options = {title:'Total Counts by Daypart by Day',
width : 600,
showRowNumber: true};
// Report Table Options
var options2 = {'title':'Total Counts by Daypart by Day',
'width':600};
// If we have already retrieved weather data, make sure it is reapplied to the table
if (weatherData.length) {
for (n=0; n<daysCovered.length; n++) {
data.setFormattedValue(n,6,weatherData[n]);
}
}
table.draw(data,options);
table2.draw(data,options2);
}
// Call to draw the chart once Google visualization is loaded
function drawChart() {
var options = {
title: 'Events by Day and Daypart',
width: 700,
height: 400,
legend: { position: 'side', maxLines: 3, textStyle: {color: 'blue', fontSize: 10, } },
bar: { groupWidth: '75%' },
hAxis: {
title: 'Date',
format: 'mm/dd',
viewWindow: {
min: [7, 30, 0],
max: [17, 30, 0]
}},
isStacked: true,
};
chart.draw(chartdata, options);
}
//Function to format a date string to match WeatherBug's Requirements
function formatDate() {
var currentDay = 0;
for (i = 0; i < datapoints.length; i++) {
var loopDay = datapoints[i].getDate();
if (loopDay != currentDay) {
var tempString = "";
daysCovered[daysCoveredIndex] = datapoints[i].getFullYear().toString();
if (datapoints[i].getMonth() <10) {
tempString = "0";
}
daysCovered[daysCoveredIndex] += tempString + (datapoints[i].getMonth() + 1).toString();
if (datapoints[i].getDate() < 10) {
tempString = "0";
}
else {
tempString = "";
}
daysCovered[daysCoveredIndex] += tempString + datapoints[i].getDate().toString();
daysCoveredIndex++;
currentDay = datapoints[i].getDate();
}
}
console.log(daysCovered); // Debug line for daysCovered array
}
//This function will apply filtering to the datapoints array and create the filteredDatapoints array
function filterArray(delta, doubleDelta) {
var current;
var next;
var fi = 0;
filteredDatapoints = []; // clear and build anew
if (delta) { // Moderate Filter (delta,0)
for (i=0; i < datapoints.length; i++) {
filteredDatapoints[fi] = datapoints[i];
current = datapoints[i];
next = datapoints[i+1];
if (next-current <= delta) {
i++;
}
fi++;
}
}
else {
var runningTotal = 0;
var n = 1;
for (i=0; i < datapoints.length; i++) {
filteredDatapoints[fi] = datapoints[i];
current = datapoints[i];
next = datapoints[i+1];
runningTotal += next - current;
while (runningTotal/n <= doubleDelta ) { // compare the average delta to the doubleDelta
n++; // for computing the average delta
i++; // running average is less than the doubleDelta - in the while loop skipping data points
current = datapoints[i];
next = datapoints[i+1];
runningTotal += next - current; // Keeps a running total of the deltas
}
n = 1;
fi++;
runningTotal = 0; // reset the running average and keep plowing through the datapoints array
}
}
}
// Here is where we will populate the table for the Refine and Report tab
function populateTable(array) {
var currentDay, currentDateString, currentDate, currentHour;
var rowSum = 0;
var tableSum = 0;
var morningSum = 0;
var afternoonSum = 0;
var eveningSum = 0;
var nightSum = 0;
var morning = 0;
var afternoon = 0;
var evening = 0;
var night = 0;
currentDateTime(array[0]);
// We will now start stepping through the array and summing for each unique day
for (i = 0; i < array.length; i++) {
var loopDay = array[i].getDate();
if (loopDay === currentDay) {
currentHour = array[i].getHours();
evalTime(currentHour);
}
else {
// Here because it is a new day. Close out the old
rowSum = morning + afternoon + evening + night;
tableSum += rowSum;
data.addRows([[currentDateString, morning, afternoon, evening, night,rowSum,""]]);
chartdata.addRows([[currentDate,morning,afternoon,evening,night]]);
// And start the new
morningSum += morning;
afternoonSum += afternoon;
eveningSum += evening;
nightSum += night;
morning = afternoon = evening = night = rowSum= 0;
currentDateTime(array[i]);
evalTime(currentHour);
}
}
rowSum = morning + afternoon + evening + night;
tableSum += rowSum;
data.addRows([[currentDateString, morning, afternoon, evening, night, rowSum,""]]);
data.addRows([["Totals", morningSum, afternoonSum, eveningSum, nightSum, tableSum,""]]);
chartdata.addRows([[currentDate,morning,afternoon,evening,night]]);
// Takes the current row and populates the date and time variables
function currentDateTime(currentRow) {
currentDay = currentRow.getDate();
currentDateString = currentRow.toDateString();
currentDate = (currentRow.getMonth()+1).toString() + "/" + currentDay.toString();
currentHour = currentRow.getHours();
}
//Takes the current time and assigns the counts to the right rows
function evalTime(time) {
if (time >= 6 && time <12) morning++;
if (time >= 12 && time < 18) afternoon++;
if (time >= 18 && time < 22) evening++;
else if (time >= 22 || time < 6) night++;
}
}
//Event Handler for the Refine Tab
$('#tabs a[href="#refine"]').click('shown', function (e) {
if (!data.getNumberOfRows()) {
// We will now trim the data points based on the values from the Preview tab
datapoints.splice(-$('#bottomDrop').val(),$('#bottomDrop').val());
datapoints.splice(0,$('#topDrop').val());
// Now we will create the summary data for the Refine Tab
$('#startTime').html("This session stated on " + datapoints[0]);
$('#endTime').html("This session ended on " + datapoints[datapoints.length-1]);
$('#reportSubtitle').html("Final Report for data collection between " + datapoints[0].toLocaleDateString() + " and " + datapoints[datapoints.length-1].toLocaleDateString());
if (!daysCovered.length) formatDate();
populateTable(datapoints);
drawTable();
drawChart();
$('#weatherButton').on('click', function () {
for (n=0; n < daysCovered.length; n++) {
getWeather(n);
if (n === 9) break; // For the free WeatherUnderground API, you can only get 10 calls a minute
}
});
$('#noFilter').on('click', function () {
// No filter - just the raw data
data.removeRows(0,data.getNumberOfRows());
chartdata.removeRows(0,chartdata.getNumberOfRows());
populateTable(datapoints);
drawTable();
drawChart();
});
$('#modFilter').on('click', function () {
//This filter just looks at the difference between two datapoints and filters if it is less than twice the delay value
filterArray(2*delayValue,0);
data.removeRows(0,data.getNumberOfRows());
chartdata.removeRows(0,chartdata.getNumberOfRows());
populateTable(filteredDatapoints);
drawTable();
drawChart();
});
$('#highFilter').on('click', function () {
filterArray(0,2*delayValue);
data.removeRows(0,data.getNumberOfRows());
chartdata.removeRows(0,chartdata.getNumberOfRows());
populateTable(filteredDatapoints);
drawTable();
drawChart();
});
}
});
// JSON calls to WeatherUnderground's API
var getWeather = function(day) {
$.ajax({
url : url + "history_" + daysCovered[day] + "/q/" + $('#stInput').val() + "/" + $('#cityInput').val() + ".json",
dataType : "jsonp",
success : function(parsed_json) {
var meantemp = parsed_json.history.dailysummary[0].meantempi;
var rainfall = parsed_json.history.dailysummary[0].rain;
weatherData[day] = meantemp + "F Avg / " + rainfall +"in. rain";
drawTable();
}
});
};
// Where the first interface listeners are and where we hide distractions until the user presses "start"
var main = function () {
$('#tabs').tab(); // Event handler for tabs
// Event Handler for the file browse button
$(document).on('change', '.btn-file :file', function() {
var input = $(this),
numFiles = input.get(0).files ? input.get(0).files.length : 1,
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
input.trigger('fileselect', [numFiles, label]);
});
// Event Handler for the "Start" button
$('.btn-file :file').on('fileselect', function(event, numFiles, label) {
var input = $(this).parents('.input-group').find(':text'),
log = numFiles > 1 ? numFiles + ' files selected' : label;
if( input.length ) {
input.val(log);
}
else {
if( log ) alert(log);
}
});
// The idea here is to hide everything until they click "start"
$('h4').hide();
$(".form-inline, .form, .form-buttons, .drop-dialog").hide();
$('#table_div, #chart_div,#table2_div').hide();
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawTable);
google.setOnLoadCallback(drawChart);
};
// After the page loads, we start with main
$(document).ready(main);
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |