In the following I will be going over the CSS, HTML5 and Javascript used to code my cute little app that ranks American Football player Offensive Lineman according to data provided by the NFL in one of their Kaggle Challenges. This app is called “O-Line” which you can see at https://feirmeoirsonrai.me/oline/

And if you want to geek out you can read my paper on the rankings system at Defending the Edge: Evaluating OT Performance through Euclidean Measurements
To get a picture of the different components involved in the User Interface for this web app we can take a quick look at the code of the <head> element in the index page HTML:
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="american football data analytics" />
<meta name="author" content="michael mccarron macciarain@protonmail.com" />
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="pragma" content="no-cache" />
<title>0-Line: NFL Analytics</title>
<!-- JS JS JS JS JS-->
<script src="./js/jquery-3.6.3.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<!-- Plotly.js -->
<script src="./js/plotly-latest.min.js"></script> <!-- plotly vers plotly.js v1.58.5-->
<script src="./js/d3.v7.min.js"></script>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" href="assets/img/logos/o.ico" />
<!-- Font Awesome icons (free version)-->
<script src="fontawesome/js/all.js" crossorigin="anonymous"></script>
<!-- CSS CSS CSS -->
<!-- Google fonts-->
<!-- Bootstrap version: Bootstrap v5.1.3 (https://getbootstrap.com/) -->
<link href="css/montserrat.css" rel="stylesheet" type="text/css" />
<link href="css/roboto.css" rel="stylesheet" type="text/css" />
<!-- Core theme CSS (includes Bootstrap)-->
<link rel="stylesheet" href="./css/bootstrap.min.css">
<link href="css/styles.css" rel="stylesheet" />
<link href="fontawesome/css/all.css" rel="stylesheet" />
</head>
Let’s first note the Javascript libraries used in this project, they are Bootstrap, jQuery, Plotly and D3. We shall go over these libraries momentarily. Next we shall notice the CSS libraries used such as Bootstrap again, Fontawesome and the custom css for this project in css/styles.css.
Bootstrap
Bootstrap (formerly Twitter Bootstrap) is a free and open-source CSS framework directed at responsive, mobile-first front-end web development. It contains HTML, CSS and (optionally) JavaScript-based design templates for typography, forms, buttons, navigation, and other interface components.
As of May 2023, Bootstrap is the 17th most starred project (4th most starred library) on GitHub, with over 164,000 stars. According to W3Techs, Bootstrap is used by 19.2% of all websites.
https://en.wikipedia.org/wiki/Bootstrap_(front-end_framework)
The thing I like about Bootstrap is it’s popularity, which means it’s tried and trusted by many front-end developers, as well there are plenty of tutorials on all aspects of Bootstrap. It does have some advanced features such as Sass and CSS variables.
One can get started with Sass at the W3Schools site, something I have used often in my coding life, they relate:
- Sass stands for Syntactically Awesome Stylesheet
- Sass is an extension to CSS
- Sass is a CSS pre-processor
- Sass is completely compatible with all versions of CSS
- Sass reduces repetition of CSS and therefore saves time
- Sass was designed by Hampton Catlin and developed by Natalie Weizenbaum in 2006
- Sass is free to download and use
CSS variables can be seen in the following quick example:
<html>
<head>
<style>
:root {
--main-bg-color: lightblue;
--main-text-color: darkblue;
}
body {
background-color: var(--main-bg-color);
color: var(--main-text-color);
}
</style>
</head>
<body>
<h1>Welcome to My Website</h1>
<p>This is a sample paragraph demonstrating CSS variables.</p>
</body>
</html>
we call the variable through the syntax: var(), then define the values of the function in the css pseudo class :root{}. A css pseudo class is:
A pseudo-class is used to define a special state of an element.
For example, it can be used to:
- Style an element when a user moves the mouse over it
- Style visited and unvisited links differently
- Style an element when it gets focus
- Style valid/invalid/required/optional form elements
Many of these things used to be accomplished by using the old jQuery library that we older developers are more used to but if your starting out read the previous link. 🙂
In my css files for example I use the :root pseudo class in the following way:
:root {
--bs-blue: #0d6efd;
--bs-indigo: #6610f2;
--bs-purple: #6f42c1;
--bs-pink: #d63384;
--bs-red: #dc3545;
...
--bs-body-bg: #fff;
}
<!-- file: css/styles.css -->
<!-- note the use of '...' just is filler for missing code -->
then for instance in the styles.css file later on when defining attributes of the <body> element we use the variable call:
body {
...
background-color: var(--bs-body-bg);
...
}
This makes writing the css alot easier and more succinct, taking less time once you are used to the syntax and overall operations of bootstrap. Back in the day when the WWW was in it’s infancy, when I got started, things like this would keep one busy for hours on end as each element would need to be individually coded up instead of using classes and variables.
Fontawesome is a convenient library to deal with icons, you can read more about it at https://fontawesome.com/
The main javascript libraries used in this comprised of Bootstrap, which is used for such common UI elements like the carousel, styling forms and buttons etc programmatically without having to create one’s own functions to achieve those things. The two main snazzy libraries used for some uncommon UI elements such as plots and animations is that of Plotly and d3.
I use d3 version 7 in this project, a brief overview of d3.js:
D3.js (also known as D3, short for Data-Driven Documents) is a JavaScript library for producing dynamic, interactive data visualizations in web browsers. It makes use of Scalable Vector Graphics (SVG), HTML5, and Cascading Style Sheets (CSS) standards. It is the successor to the earlier Protovis framework. Its development was noted in 2011, as version 2.0.0 was released in August 2011. With the release of version 4.0.0 in June 2016, D3 was changed from a single library into a collection of smaller, modular libraries that can be used independently.
The D3.js library uses pre-built functions to select elements, create SVG objects, style them, or add transitions, dynamic effects, or tooltips. These objects can also be styled using CSS. Large datasets can be bound to SVG objects using D3.js functions to generate text/graphic charts and diagrams. The data can be in various formats such as JSON, comma-separated values (CSV) or geoJSON, but, if required, JavaScript functions can be written to read other data formats.
(source: https://en.wikipedia.org/wiki/D3.js )
Later, I shall go into the technical details of implementing a data-driven web app relying only on Javascript with no server-side backend, such as Ruby, Python, PHP, ASP or Java, using JS and CVS only.
Plotly is a javascript library that provides a UI for web based data visualizations, many Python based Data Science developers are familiar with Plotly in Python, in this case it is for HTML. Built on top of d3.js and stack.gl, Plotly.js is a high-level, declarative charting library. plotly.js ships with over 40 chart types, including 3D charts, statistical graphs, and SVG maps. plotly.js is free and open source and you can view the source, report issues or contribute on GitHub.
Now that we have an overview of the CSS and JS involved in this web app, we can start to take a deeper dive into the code. As previously stated this web application is one that is based solely in HTML5, CSS and Javascript, though not a single-page-application, which is to me is an overstated desire that often is not functional except for simple sites, it is a app that has no back-end. For those new to the field in the good old days most dynamic sites used such back-end programming languages like Ruby, PHP, Java, etc to serve up dynaic content. This project however does not rely on any of those ‘back-end’ languages but relies on the ‘front-end’ Javascript Language, which is a derivative of ECMAScript, the latest version being ECMAScript 2023 or 14, it should not be confused with the programming language Java, a seperate ball of wax. This project was based in version 6. This project also does not deal with typescript, although in another project I shall be going over TypeScript, which is a strongly typed programming language that builds on JavaScript, for programmers the basic additive of types to JS, which is normally the domain of languages like PHP, Java, etc.
Now with all that nitty gritty out of the way, we can get to the bone of this cut of meat. The first thing I would like to address is how I dealth with replacing standard url calls for dynamic pages, where ?something=value&anothersomething=anothervalue is encountered which is done using ‘back-end’ languages. I used hashes to seperate my url calls so that the for instance the url call:
https://feirmeoirsonrai.me/oline/play.html#42476_gameId:2021092603
has the agrument from the right of the hash tag (‘#’) in the url of the players_id and the the gameid. I created a simple Javascript parser to deal with parsing the hashtag.
<script>
var hash = $(location).prop('hash').substr(1);
let uri_args = words = hash.split(':');
console.log(uri_args);
player_id = uri_args[0];
player_id = player_id.split('_');
player_id = player_id[0];
game_id = uri_args[1];
play_id = uri_args[2];
console.log("game id: "+game_id);
console.log("play_id: "+play_id);
console.log("player id: "+player_id);
</script>
the variable hash retrieves the url.location ‘object’ using jQuery, a bit outdated method but one I am used to, and then using the split method based on the colon symbol ‘:’ divides the string into two parts, the first part uri_args[0] gives us the player_id which is ‘42476’ and the second part gives us the game_id which is 2021092603
This then serves up the 18 plays for the game of the player:

For serving up data, I have parsed all my data into csv file format and json format. For instance, the data for https://feirmeoirsonrai.me/oline/team.html#ARI
is dynamically served up from the JSON file:
{"2021091207":2.8938053097,"2021091909":2.9089285714,"2021092603":2.9267241379,"2021100309":2.8297546012,"2021101011":2.850877193,"2021101708":2.869140625,"2021102408":2.8168103448,"2021102800":2.8934782609}
here we have a simple 2 level object notation for each game by game_id and the overall rating of the Offensive line for that game ie. 2.8938053097. Which is then put into the UI for presentation.
you can learn more about JSON at https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/JSON
The nuts and bolts of presenting this in the UI is handled by Javascript through the following code:
team_data = [];
$(document).ready(function(){
p_id = player_id;
$.getJSON("./js/json/teams/"+team+"/summary.json", function(data){
cnt = 0;
details = document.getElementById( "games" )
team_name = document.getElementById( "team_name");
$(team_name).html(team);
plays_text = "";
for (const key in data) {
if (data.hasOwnProperty(key)) {
console.log(`${key}: ${data[key]}`);
let p_data = `${key}: ${data[key]}`;
t_qbi = data[key];
team_data.push(t_qbi);
game_id = key;
t_qbi = (Math.round(t_qbi * 100) / 100).toFixed(2);
console.log(game_id + " -- "+ t_qbi);
var play_plot = "Team QBI for game: "+t_qbi+" <a class='research-link highlight' href='game.html#"+team+":"+game_id+"'>; View Game Details: "+game_id+" </a><br />\n";
plays_text = play_plot;
$(details).append(plays_text + "<hr>");
}
}
const arr_avg = arr => arr.reduce((a,b) => a + b, 0) / arr.length
season_qbi_avg = arr_avg(team_data);
season_qbi_avg = (Math.round(season_qbi_avg * 100) / 100).toFixed(2);
let season_qbi = "<small>Week 1-8 of 2021 Collective QBI:</small> "+season_qbi_avg;
console.log(season_qbi);
spin = document.getElementById( "spin" )
$( spin ).hide();
team_qbi = document.getElementById("team_qbi");
$( team_qbi ).html(season_qbi);
}).fail(function(){
console.log("An error has occurred.");
});
});
which is in the file js/handlers/teams.js being a JS based project and since their is no privileged user data or any other secure data all my code is open to the public if you have a browser and want to navigate to it one can and then reverse engineer everything oneself.
As a coder the funniest and most challenging aspect of this application was the plotting of each play, I wanted to show step by step how the team blocked on each play so one could trace out how the play developed. This was envisioned primarily as an educational tool for offensive line players so they could see the overall picture of what constituted a bad rating and a good rating. This is all done using Javascript, JSON and CVS in a HTML5 UI Layer, below we will walk through the code to see how this is done using Plotly. See the following link for a live example: https://feirmeoirsonrai.me/oline/games_plot.html#2021091207:152
One is able to view the play from moment to moment as it unfolds, so that at time 0 we see:

and then at a later time at t=37, which is actually 3.7sec into the play:

One can also see a path trace of the same play I compiled and put in MP4 format:
https://feirmeoirsonrai.me/oline/plots/plots/2021091207/2021091207_152_all.mp4
When we look at games_plot.html we see that it relies on a JS handler file to do the heavy lifting, this is in js/handlers/plot11_on_11.js. One can download the code file as is at https://feirmeoirsonrai.me/oline/js/handlers/plot_11on11.js
The first bit of lifting is getting the data which I use builtin functions of d3 to do:
Plotly.d3.csv("./csv/all/"+game_id+"/all_"+play_id+".csv", function (data) {
function iterateObject(obj) {
for (const key in obj) {
console.log(obj);
if (obj.hasOwnProperty(key)) {
console.log(`${key}: ${obj[key]}`);
}
}
}
this pulls in a dynamaically referenced csv file, where the data is in comma seperated format which is then accessible in the ‘data’ variable. We then set up some simple X,Y coordinates, which is also common to any datascience plot, then create a object to assign the data to specific parts of the object:
if (!(trace = bytime_frame[role])) {
trace = bytime_frame[role] = {
x: [],
y: [],
id: [],
text: [],
marker: {size: []}
};
}
so that we end up with different arrays, such as x,y, id, text, marker
we then iterate the data and push the specific data to the right object part:
// Go through each row, get the right trace, and append the data:
for (var i = 0; i < data.length; i++) {
var datum = data[i];
var trace = getData(datum.time_frame, datum.role);
trace.text.push(datum.position);
trace.id.push(datum.position);
trace.x.push(datum.x);
trace.y.push(datum.y);
trace.marker.size.push(20000000);
}
then we slice the data up and put into a traces array
// Create the main traces, one for each role:
var traces = [];
for (i = 0; i < roles.length; i++) {
var data = firsttime_frame[roles[i]];
var marker_color = "";
if(i == 0){
marker_color = '#000000';
} else {
marker_color = '#e64a17';
}
traces.push({
name: roles[i],
x: data.x.slice(),
y: data.y.slice(),
id: data.id.slice(),
text: data.text.slice(),
mode: 'markers',
marker: {
size: data.marker.size.slice(),
sizemode: 'area',
color: marker_color,
sizeref: 175000
}
//type: 'scatter'
});
after this we don’t need to do much more custom handling of the data and the rest is part of the standard operating procedure for serving up any plot. See the link above to the js file to see the whole picture.
This was a fun project to develop and as it was my own project I had free license to do what I wanted with it and decided to have some fun. I hope you enjoy it’s different way of handling data and lack of a ‘back-end’. As an experienced developer can see such a methodology works well for sites not requiring secure logins, for informational sites such a setup can easily replace andy LAMP stack backends and rely solely on front-end technology to get the job done although we are dealing with MBs of data.
Leave a Reply