Cleaning up for going live

This commit is contained in:
BooshPC 2025-02-08 22:27:38 -05:00
parent 33f10ee40b
commit ab38aa24e4
9 changed files with 6762 additions and 4131 deletions

File diff suppressed because it is too large Load Diff

@ -1,59 +1,4 @@
# .\NFLOddsVis
# .\What Are the Odds?
This project came about as a coping mechanism. As an Eagles fan, I've spent the past two weeks after the NFC Championship thinking about how accurate predictions are when it comes to NFL game odds. I wanted to visualize how often odds-makers are accurateand to what extent. I was able to find nearly every game's odds going back to 1952 thanks to [Sports Odds History](https://www.sportsoddshistory.com/nfl-game-odds/), used Python and [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/) to parse the HTML into [json'ed data](https://git.schaffz.in/gabi/NFLOddsVis/) (I'm working on mirroring to GitHub), and then [Observable D3](https://d3js.org/) to build the graphs.
This is an [Observable Framework](https://observablehq.com/framework/) app. To install the required dependencies, run:
```
npm install
```
Then, to start the local preview server, run:
```
npm run dev
```
Then visit <http://localhost:3000> to preview your app.
For more, see <https://observablehq.com/framework/getting-started>.
## Project structure
A typical Framework project looks like this:
```ini
.
├─ src
│ ├─ components
│ │ └─ timeline.js # an importable module
│ ├─ data
│ │ ├─ launches.csv.js # a data loader
│ │ └─ events.json # a static data file
│ ├─ example-dashboard.md # a page
│ ├─ example-report.md # another page
│ └─ index.md # the home page
├─ .gitignore
├─ observablehq.config.js # the app config file
├─ package.json
└─ README.md
```
**`src`** - This is the “source root” — where your source files live. Pages go here. Each page is a Markdown file. Observable Framework uses [file-based routing](https://observablehq.com/framework/project-structure#routing), which means that the name of the file controls where the page is served. You can create as many pages as you like. Use folders to organize your pages.
**`src/index.md`** - This is the home page for your app. You can have as many additional pages as youd like, but you should always have a home page, too.
**`src/data`** - You can put [data loaders](https://observablehq.com/framework/data-loaders) or static data files anywhere in your source root, but we recommend putting them here.
**`src/components`** - You can put shared [JavaScript modules](https://observablehq.com/framework/imports) anywhere in your source root, but we recommend putting them here. This helps you pull code out of Markdown files and into JavaScript modules, making it easier to reuse code across pages, write tests and run linters, and even share code with vanilla web applications.
**`observablehq.config.js`** - This is the [app configuration](https://observablehq.com/framework/config) file, such as the pages and sections in the sidebar navigation, and the apps title.
## Command reference
| Command | Description |
| ----------------- | -------------------------------------------------------- |
| `npm install` | Install or reinstall dependencies |
| `npm run dev` | Start local preview server |
| `npm run build` | Build your static site, generating `./dist` |
| `npm run deploy` | Deploy your app to Observable |
| `npm run clean` | Clear the local data loader cache |
| `npm run observable` | Run commands like `observable help` |
A mobile/responsive version of this is in the works and I plan to work on this to add more contextual layers, labels, and clarity. I'd love to hear your feedback on it, so please reach out on [IG](https://www.instagram.com/gabi.schaffz.in/) or [Bluesky](https://bsky.app/profile/gabi.schaffz.in).

@ -34,7 +34,7 @@ for year in tqdm(range(start_year, 2025)):
cursor = cursor+1
spreadText = cells[cursor+6].string
if re.match(r'.\s-?\d', spreadText):
if re.match(r'.\s-?(\d|PK)', spreadText):
game["date"] = cells[cursor+1].string
game["at"] = cells[cursor+3].string
game["fav"] = cells[cursor+4].string
@ -45,12 +45,16 @@ for year in tqdm(range(start_year, 2025)):
game["sF"] = actualScore.group(1)
game["sU"] = actualScore.group(2)
spreadValSearch = re.search(r'.\s-?(\d*\.*\d)', spreadText)
spreadValSearch = re.search(r'.\s-?(\d*\.*\d|PK)', spreadText)
if spreadValSearch:
spreadVal = spreadValSearch.group(1)
if spreadVal=="PK":
spreadVal = 0
else:
spreadVal = -1
ou = []
for t in cells[cursor+9].string.split():
try:

@ -1,10 +1,10 @@
import requests
for year in range(1957, 2025):
for year in range(2024, 2025):
URL = "https://www.sportsoddshistory.com/nfl-game-season/?y="+str(year)
page = requests.get(URL)
f = open("data/"+str(year)+".html", "w", encoding="utf-8")
f = open("src/data/"+str(year)+".html", "w", encoding="utf-8")
f.write(page.text)
f.close

@ -2,6 +2,8 @@
<!DOCTYPE html>
<!--[if IE 7]>
@ -245,6 +247,7 @@ https://cdn.jsdelivr.net/npm/jqdoublescroll@1.0.0/jquery.doubleScroll.min.js
<style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style>
<!-- All in One SEO Pro 4.7.8 - aioseo.com -->
<title>Historical NFL Game Odds by Season |SportsOddsHistory.com</title>
<meta name="robots" content="max-image-preview:large" />
<link rel="canonical" href="https://www.sportsoddshistory.com/nfl-game-season/" />
@ -315,7 +318,9 @@ https://cdn.jsdelivr.net/npm/jqdoublescroll@1.0.0/jquery.doubleScroll.min.js
<link rel="alternate" title="oEmbed (JSON)" type="application/json+oembed" href="https://www.sportsoddshistory.com/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fwww.sportsoddshistory.com%2Fnfl-game-season%2F" />
<link rel="alternate" title="oEmbed (XML)" type="text/xml+oembed" href="https://www.sportsoddshistory.com/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fwww.sportsoddshistory.com%2Fnfl-game-season%2F&#038;format=xml" />
<style type="text/css">
ul.cnss-social-icon li.cn-fa-icon a:hover{opacity: 0.7!important;color:#ffffff!important;}
</style><style id="ubermenu-custom-generated-css">
/** Font Awesome 4 Compatibility **/
.fa{font-style:normal;font-variant:normal;font-weight:normal;font-family:FontAwesome;}
@ -426,11 +431,17 @@ label{
</div><!--social-->
<div class="search_item">
<form role="search" method="get" id="searchform" action="https://www.sportsoddshistory.com/">
<div><label class="screen-reader-text" for="s"></label>
<input type="submit" id="searchsubmit" value="Search" />
<input type="text" value="" name="s" id="s" />
</div>
</form>
</div>
<div class="mega_menu">
@ -493,6 +504,7 @@ label{
<p style="font-size:x-small"><a href="https://www.sportsoddshistory.com/">Home</a>&nbsp;>&nbsp;
<a href='https://www.sportsoddshistory.com/nfl-game-odds/'>Historical NFL Game Odds</a>&nbsp;>&nbsp;1952 Season</p>
@ -508,6 +520,7 @@ label{
<table class='soh1' onMouseover='changeto(event, &quot;#FFFFCC&quot;)' onMouseout='changeback(event, &quot;white&quot;)'>
<tr id='ignore' bgcolor='#C9C9C9'>
<th rowspan=2><a href="https://www.sportsoddshistory.com/nfl-game-season/?y1952=&o=w">Week</th>
@ -703,6 +716,7 @@ label{
<table class='soh1' onMouseover='changeto(event, &quot;#FFFFCC&quot;)' onMouseout='changeback(event, &quot;white&quot;)'>
<tr id='ignore' bgcolor='#C9C9C9'>
<th rowspan=2><a href="https://www.sportsoddshistory.com/nfl-game-season/?y1952=&to=tw#t">Team</th>
@ -893,6 +907,7 @@ label{
<td>
<a id=1><br></a><hr>
<p align=right><a href="#top">Back to top</a></p><h3>1952 Regular Season - Week 1</h3><p><b>BOLD = team that covered the spread</b></p>
@ -1070,7 +1085,7 @@ label{
<td></td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=SFF&d=1950#1952" ><b>San Francisco 49ers</b></a></td>
<td>W 37-14</td>
<td>W -14.5</td>
<td>L PK</td>
<td>@</td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=DTX&d=1950#1952" >Dallas Texans</a></td>
<td> </td>
@ -2292,23 +2307,41 @@ label{
<table><tr><td align="center">
<p class = 'sohpn'>Historical odds/data on this site are believed to be accurate. <a href="mailto:sohistory09@gmail.com">Please let us know</a> if you find any issues.</p><br>
<p class = 'sohpn'>Gambling problem? Call 1-800-GAMBLER CO, DC, IL, IN, KS, KY, LA, MD, MS, NJ, OH, PA, TN, VA, WV, WY <br>
Call 877-8-HOPENY or text HOPENY (467369) (NY) <br>
Call 1-800-327-5050 (MA) 21+ to wager. <br>
Please Gamble Responsibly. Call 1-800-NEXT-STEP (AZ), 1-800-522-4700 (NV), 1-800-BETS-OFF (IA), 1-800-270-7117 for confidential help (MI), 1-800-981-0023 (PR). <br>
In partnership with Kansas Crossing Casino and Hotel. Visit BetMGM.com for Terms & Conditions. US promotional offers not available in DC, Kansas, Nevada, New York or Ontario</p>
<br>
<!-- <p class = 'sohpn'><a href="https://www.bettingfans.com/" target=_blank>BettingFans.com - Join the world's next-level sports betting community</a></p> -->
</td></tr></table>
<div class="footer test-footer">
<div class="footer_top">
<div class="menu-footer-container"><ul id="menu-footer" class=""><li id="menu-item-780" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-home menu-item-780"><a href="http://www.sportsoddshistory.com/">Home</a></li>
<li id="menu-item-537" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-537"><a href="https://www.sportsoddshistory.com/nfl-odds/">NFL</a></li>
<li id="menu-item-83" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-83"><a href="https://www.sportsoddshistory.com/nba-odds/">NBA</a></li>
@ -2322,51 +2355,92 @@ label{
<li id="menu-item-782" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-782"><a href="https://www.sportsoddshistory.com/other/">Other</a></li>
<li id="menu-item-781" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-781"><a href="https://www.sportsoddshistory.com/category/blog/">Blog</a></li>
</ul></div>
</div><!--footer_top-->
<div class="footer_mid abc">
<div class="footer_logos_wrp">
<div class="wrapper">
<h3>As seen on</h3>
<ul>
<li><a href="https://fivethirtyeight.com/" target="_blank" name="538 link"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbQAAAA4AQAAAAC9SjD8AAAAAnRSTlMAAHaTzTgAAAAaSURBVEjH7cGBAAAAAMOg+VNf4QBVAQAAfAYMQAABdrcIaQAAAABJRU5ErkJggg==" alt="538 logo" data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/five_thirty_eighty.png" decoding="async" class="lazyload" data-eio-rwidth="436" data-eio-rheight="56"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/five_thirty_eighty.png" alt="538 logo" data-eio="l"></noscript></a></li>
<li><a href="https://www.actionnetwork.com/" target="_blank" name="Action Network link"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAX4AAAB6AQAAAACQRwXzAAAAAnRSTlMAAHaTzTgAAAAdSURBVFjD7cGBAAAAAMOg+VNf4QBVAQAAAAAAvwEXWgABCCa+YAAAAABJRU5ErkJggg==" alt="Action Network logo" data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/the_action_network.png" decoding="async" class="lazyload" data-eio-rwidth="382" data-eio-rheight="122"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/the_action_network.png" alt="Action Network logo" data-eio="l"></noscript></a></li>
<li><a href="https://www.espn.com/" target="_blank" name="ESPN.com link"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAAvAQAAAABWbhwDAAAAAnRSTlMAAHaTzTgAAAASSURBVDjLY2AYBaNgFIwCEgEABDkAAbpRxzgAAAAASUVORK5CYII=" alt="ESPN.com logo" data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sony_spn.png" decoding="async" class="lazyload" data-eio-rwidth="172" data-eio-rheight="47"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sony_spn.png" alt="ESPN.com logo" data-eio="l"></noscript></a></li>
<li><a href="https://www.nytimes.com/" target="_blank" name="New York Times link"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjcAAABLAQAAAACkLJuQAAAAAnRSTlMAAHaTzTgAAAAbSURBVFjD7cEBAQAAAIIg/69uSEABAAAAAHwZFRgAASO/PgkAAAAASUVORK5CYII=" alt="New York Times logo" data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/nytimes.png" decoding="async" class="lazyload" data-eio-rwidth="567" data-eio-rheight="75"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/nytimes.png" alt="New York Times logo" data-eio="l"></noscript></a></li>
<li><a href="https://www.si.com/" target="_blank" name="Sports Illustrated link"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfYAAAA1AQAAAACbm/S/AAAAAnRSTlMAAHaTzTgAAAAaSURBVEjH7cGBAAAAAMOg+VNf4QBVAQAA8BgNQAABeKmSTAAAAABJRU5ErkJggg==" alt="Sports Illustrated logo" data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sports_illustrated.png" decoding="async" class="lazyload" data-eio-rwidth="502" data-eio-rheight="53"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sports_illustrated.png" alt="Sports Illustrated logo" data-eio="l"></noscript></a></li>
<!--
<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbQAAAA4AQAAAAC9SjD8AAAAAnRSTlMAAHaTzTgAAAAaSURBVEjH7cGBAAAAAMOg+VNf4QBVAQAAfAYMQAABdrcIaQAAAABJRU5ErkJggg==" width=163 data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/five_thirty_eighty.png" decoding="async" class="lazyload" data-eio-rwidth="436" data-eio-rheight="56"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/five_thirty_eighty.png" width=163 data-eio="l"></noscript></li>
<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAX4AAAB6AQAAAACQRwXzAAAAAnRSTlMAAHaTzTgAAAAdSURBVFjD7cGBAAAAAMOg+VNf4QBVAQAAAAAAvwEXWgABCCa+YAAAAABJRU5ErkJggg==" width=163 data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/the_action_network.png" decoding="async" class="lazyload" data-eio-rwidth="382" data-eio-rheight="122"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/the_action_network.png" width=163 data-eio="l"></noscript></li>
<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAAvAQAAAABWbhwDAAAAAnRSTlMAAHaTzTgAAAASSURBVDjLY2AYBaNgFIwCEgEABDkAAbpRxzgAAAAASUVORK5CYII=" width=163 data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sony_spn.png" decoding="async" class="lazyload" data-eio-rwidth="172" data-eio-rheight="47"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sony_spn.png" width=163 data-eio="l"></noscript></li>
<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAjcAAABLAQAAAACkLJuQAAAAAnRSTlMAAHaTzTgAAAAbSURBVFjD7cEBAQAAAIIg/69uSEABAAAAAHwZFRgAASO/PgkAAAAASUVORK5CYII=" width=163 data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/nytimes.png" decoding="async" class="lazyload" data-eio-rwidth="567" data-eio-rheight="75"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/nytimes.png" width=163 data-eio="l"></noscript></li>
<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfYAAAA1AQAAAACbm/S/AAAAAnRSTlMAAHaTzTgAAAAaSURBVEjH7cGBAAAAAMOg+VNf4QBVAQAA8BgNQAABeKmSTAAAAABJRU5ErkJggg==" width=163 data-src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sports_illustrated.png" decoding="async" class="lazyload" data-eio-rwidth="502" data-eio-rheight="53"><noscript><img src="https://www.sportsoddshistory.com/wp-content/themes/sports/images/sports_illustrated.png" width=163 data-eio="l"></noscript></li>
-->
</ul>
</div>
</div>
</div><!--footer_mid -->
<div class="footer_bot">
<div class="menu-footer-bottom-container"><ul id="menu-footer-bottom" class=""><li id="menu-item-536" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-536"><a href="https://www.sportsoddshistory.com/about-us/">About Us</a></li>
<li id="menu-item-532" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-532"><a href="https://www.sportsoddshistory.com/terms-of-use/">Terms of Use</a></li>
<li id="menu-item-43776" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-43776"><a href="https://www.sportsoddshistory.com/partners/">Partners</a></li>
<li id="menu-item-533" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-533"><a href="https://www.sportsoddshistory.com/sitemap/">Sitemap</a></li>
<li id="menu-item-535" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-535"><a href="https://www.sportsoddshistory.com/contact-us/">Contact Us</a></li>
</ul></div>
</div><!--footer_bot-->
</div><!--footer-->
</div><!--wrapper-->
</div><!--container-->
</body>
<script type="text/javascript" src="https://www.sportsoddshistory.com/wp-includes/js/jquery/ui/core.min.js?ver=1.13.3" id="jquery-ui-core-js"></script>
<script type="text/javascript" src="https://www.sportsoddshistory.com/wp-includes/js/jquery/ui/mouse.min.js?ver=1.13.3" id="jquery-ui-mouse-js"></script>
<script type="text/javascript" src="https://www.sportsoddshistory.com/wp-includes/js/jquery/ui/sortable.min.js?ver=1.13.3" id="jquery-ui-sortable-js"></script>
@ -2386,4 +2460,6 @@ var ubermenu_data = {"remove_conflicts":"on","reposition_on_load":"off","intent_
<script type="text/javascript" src="https://www.sportsoddshistory.com/wp-content/plugins/ubermenu/assets/js/ubermenu.min.js?ver=3.8.5" id="ubermenu-js"></script>
</body>
</html>

@ -244,11 +244,11 @@ https://cdn.jsdelivr.net/npm/jqdoublescroll@1.0.0/jquery.doubleScroll.min.js
<style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style>
<!-- All in One SEO Pro 4.7.8 - aioseo.com -->
<!-- All in One SEO Pro 4.7.9 - aioseo.com -->
<title>Historical NFL Game Odds by Season |SportsOddsHistory.com</title>
<meta name="robots" content="max-image-preview:large" />
<link rel="canonical" href="https://www.sportsoddshistory.com/nfl-game-season/" />
<meta name="generator" content="All in One SEO Pro (AIOSEO) 4.7.8" />
<meta name="generator" content="All in One SEO Pro (AIOSEO) 4.7.9" />
<meta property="og:locale" content="en_US" />
<meta property="og:site_name" content="SportsOddsHistory.com | Archived futures lines of the Super Bowl, World Series &amp; more" />
<meta property="og:type" content="article" />
@ -746,15 +746,15 @@ label{
</tr>
<tr>
<td align="center"><a href="https://www.sportsoddshistory.com/nfl-game-season/?y=2024#30" >Playoffs</a></td>
<td align=left style="font-size: 11px;">5-5 (50.0%)</td>
<td align=left style="font-size: 11px;">3-7-0 (30.0%)</td>
<td align=left style="font-size: 11px;">7-5 (58.3%)</td>
<td align=left style="font-size: 11px;">5-7-0 (41.7%)</td>
<td align=left style="font-size: 11px;">9-2 (81.8%)</td>
<td align=left style="font-size: 11px;">7-4-0 (63.6%)</td>
<td align=left style="font-size: 11px;">7-2 (77.8%)</td>
<td align=left style="font-size: 11px;">5-4-0 (55.6%)</td>
<td align=left style="font-size: 11px;">5-2 (71.4%)</td>
<td align=left style="font-size: 11px;">3-4-0 (42.9%)</td>
<td align=left style="font-size: 11px;">2-0 (100.%)</td>
<td align=left style="font-size: 11px;">2-0-0 (100.%)</td>
<td align=left style="font-size: 11px;">4-6-0 (40.0%)</td>
<td align=left style="font-size: 11px;">6-6-0 (50.0%)</td>
</tr>
</tbody>
</table>
@ -5326,6 +5326,32 @@ label{
<td>@</td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=BUF&d=2020#2024" ><b>Buffalo Bills (2)</b></a></td>
<td>O 51.5</td>
</tr>
<tr>
<td>NFC Championship</td>
<td>Sun</td>
<td>Jan 26, 2025</td>
<td align='center'>3:00</td>
<td>@</td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=BAL&d=2020#2024" ><b>Philadelphia Eagles (2)</b></a></td>
<td>W 55-23</td>
<td>W -6</td>
<td></td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=WAS&d=2020#2024" >Washington Commanders (6)</a></td>
<td>O 47</td>
</tr>
<tr>
<td>AFC Championship</td>
<td>Sun</td>
<td>Jan 26, 2025</td>
<td align='center'>6:30</td>
<td>@</td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=BAL&d=2020#2024" ><b>Kansas City Chiefs (1)</b></a></td>
<td>W 32-29</td>
<td>W -1</td>
<td></td>
<td><a href="https://www.sportsoddshistory.com/nfl-game-team/?tm=BUF&d=2020#2024" >Buffalo Bills (2)</a></td>
<td>O 49.5</td>
</tr>
</tbody>
</table>

File diff suppressed because it is too large Load Diff

@ -1,4 +1,19 @@
[
{
"date": "Sep 28, 1952",
"at": "@",
"fav": "Cleveland Browns",
"und": "Los Angeles Rams",
"sF": "37",
"sU": "7",
"spread": 0,
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 1"
},
{
"date": "Sep 28, 1952",
"at": "@",
@ -8,6 +23,10 @@
"sU": "3",
"spread": "3.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 1"
},
{
@ -19,6 +38,10 @@
"sU": "14",
"spread": "14",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 2"
},
{
@ -30,6 +53,10 @@
"sU": "20",
"spread": "14",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 2"
},
{
@ -41,6 +68,10 @@
"sU": "21",
"spread": "13",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 2"
},
{
@ -50,8 +81,12 @@
"und": "Dallas Texans",
"sF": "37",
"sU": "14",
"spread": "14.5",
"spread": 0,
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 2"
},
{
@ -63,6 +98,10 @@
"sU": "20",
"spread": "17",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 3"
},
{
@ -74,6 +113,10 @@
"sU": "17",
"spread": "3",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 3"
},
{
@ -85,6 +128,10 @@
"sU": "28",
"spread": "3",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 3"
},
{
@ -96,6 +143,10 @@
"sU": "28",
"spread": "5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 3"
},
{
@ -107,6 +158,10 @@
"sU": "21",
"spread": "7",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 3"
},
{
@ -118,6 +173,10 @@
"sU": "6",
"spread": "3",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 3"
},
{
@ -129,6 +188,10 @@
"sU": "16",
"spread": "6.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 4"
},
{
@ -140,6 +203,10 @@
"sU": "16",
"spread": "6.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 4"
},
{
@ -151,6 +218,10 @@
"sU": "24",
"spread": "13",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 4"
},
{
@ -162,6 +233,10 @@
"sU": "7",
"spread": "7",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 4"
},
{
@ -173,6 +248,10 @@
"sU": "28",
"spread": "7",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 4"
},
{
@ -184,6 +263,10 @@
"sU": "34",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 5"
},
{
@ -195,6 +278,10 @@
"sU": "15",
"spread": "14",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 5"
},
{
@ -206,6 +293,10 @@
"sU": "17",
"spread": "6",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 5"
},
{
@ -217,6 +308,10 @@
"sU": "7",
"spread": "6",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 5"
},
{
@ -228,6 +323,10 @@
"sU": "14",
"spread": "15",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 5"
},
{
@ -239,6 +338,10 @@
"sU": "21",
"spread": "23",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 5"
},
{
@ -250,6 +353,10 @@
"sU": "6",
"spread": "6.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 6"
},
{
@ -261,6 +368,25 @@
"sU": "17",
"spread": "1.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 6"
},
{
"date": "Nov 2, 1952",
"at": "@",
"fav": "Green Bay Packers",
"und": "Philadelphia Eagles",
"sF": "12",
"sU": "10",
"spread": 0,
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 6"
},
{
@ -272,6 +398,10 @@
"sU": "20",
"spread": "23",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 6"
},
{
@ -283,6 +413,10 @@
"sU": "20",
"spread": "17",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 6"
},
{
@ -294,6 +428,10 @@
"sU": "24",
"spread": "2.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 6"
},
{
@ -305,6 +443,10 @@
"sU": "41",
"spread": "9",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 7"
},
{
@ -316,6 +458,10 @@
"sU": "13",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 7"
},
{
@ -327,6 +473,10 @@
"sU": "6",
"spread": "17",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 7"
},
{
@ -338,6 +488,10 @@
"sU": "23",
"spread": "2",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 7"
},
{
@ -349,6 +503,10 @@
"sU": "20",
"spread": "6",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 7"
},
{
@ -360,6 +518,10 @@
"sU": "6",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 7"
},
{
@ -371,6 +533,10 @@
"sU": "24",
"spread": "6",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 8"
},
{
@ -382,6 +548,10 @@
"sU": "28",
"spread": "14",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 8"
},
{
@ -393,6 +563,10 @@
"sU": "13",
"spread": "26.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 8"
},
{
@ -404,6 +578,10 @@
"sU": "17",
"spread": "13",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 8"
},
{
@ -415,6 +593,10 @@
"sU": "7",
"spread": "6",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 8"
},
{
@ -426,6 +608,10 @@
"sU": "17",
"spread": "14.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 8"
},
{
@ -437,6 +623,10 @@
"sU": "24",
"spread": "13",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 9"
},
{
@ -448,6 +638,10 @@
"sU": "28",
"spread": "13.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 9"
},
{
@ -459,6 +653,10 @@
"sU": "14",
"spread": "21",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 9"
},
{
@ -470,6 +668,10 @@
"sU": "9",
"spread": "1",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 9"
},
{
@ -481,6 +683,10 @@
"sU": "14",
"spread": "5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 9"
},
{
@ -492,6 +698,10 @@
"sU": "10",
"spread": "11",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 9"
},
{
@ -503,6 +713,10 @@
"sU": "24",
"spread": "7",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 10"
},
{
@ -514,6 +728,10 @@
"sU": "28",
"spread": "6",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 10"
},
{
@ -525,6 +743,25 @@
"sU": "63",
"spread": "7.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 10"
},
{
"date": "Nov 30, 1952",
"at": "@",
"fav": "San Francisco 49ers",
"und": "Los Angeles Rams",
"sF": "21",
"sU": "34",
"spread": 0,
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 10"
},
{
@ -536,6 +773,10 @@
"sU": "24",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 10"
},
{
@ -547,6 +788,10 @@
"sU": "0",
"spread": "7.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 11"
},
{
@ -558,6 +803,10 @@
"sU": "21",
"spread": "13",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 11"
},
{
@ -569,6 +818,10 @@
"sU": "27",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 11"
},
{
@ -580,6 +833,10 @@
"sU": "27",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 11"
},
{
@ -591,6 +848,10 @@
"sU": "21",
"spread": "20",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 11"
},
{
@ -602,6 +863,10 @@
"sU": "24",
"spread": "7",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 11"
},
{
@ -613,6 +878,10 @@
"sU": "7",
"spread": "1",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 12"
},
{
@ -624,6 +893,10 @@
"sU": "14",
"spread": "13",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 12"
},
{
@ -635,6 +908,10 @@
"sU": "37",
"spread": "10",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 12"
},
{
@ -646,6 +923,10 @@
"sU": "24",
"spread": "1",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 12"
},
{
@ -657,6 +938,10 @@
"sU": "27",
"spread": "3",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 12"
},
{
@ -668,6 +953,10 @@
"sU": "21",
"spread": "3",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Regular Season - Week 13"
},
{
@ -679,6 +968,10 @@
"sU": "7",
"spread": "3.5",
"ou": null,
"pScore": {
"sF": null,
"sU": null
},
"week": "1952 Playoffs"
}
]

@ -1,3 +1,7 @@
---
theme: dark
---
```js
import("https://kit.fontawesome.com/e791529cc8.js");
```
@ -32,6 +36,7 @@ const winCircleFill = "rgba(255,255,255,0.75)";
const coverCircleFill = "rgb(0,0,0,0.75)";
const favFill = "#F25781";
const undFill = "#0277D1";
const pkFill = "#DED631";
const neutFill = "#5C7553";
const neutFillSecondary = "#8F8A40"
const playOffStroke = "rgba(255,255,255,0.5)";
@ -83,7 +88,7 @@ const viewSelect = Inputs.radio(
format:(x)=>{
if(x[1]==1)
{
return html`Predicted Score <i class="fa-regular fa-circle-question"></i>`
return html`Predicted Score <i id="pred-score-qmark" class="fa-regular fa-circle-question"></i>`
}else{
return "Favorite vs. Underdog"
}
@ -93,8 +98,20 @@ const viewSelect = Inputs.radio(
const viewValue = Generators.input(viewSelect);
```
```js
d3.select("#pred-score-qmark")
.on("mouseover", function(e){
const tooltip = d3.select("#pred-score-tooltip")
tooltip.style("display","block")
tooltip.style("left",(e.clientX+10)+"px");
tooltip.style("top",(e.clientY+10)+"px");
})
.on("mouseout", function(e){
d3.select("#pred-score-tooltip").style("display","none");
});
```
<div id="pred-score-tooltip" class="lexend-200">
Predicted score takes the spread and over/under to calculate what odds-makers would have predicted the final score to be, and evaluates its accuracy based on the square-root of the sum of squares of the predicted favorite score and underdog score.
</div>
```js
let diffColorScale;
@ -120,15 +137,16 @@ function oddsPlot(d, {width} = {}) {
const yearN = d3.max(data, (d) => d.year)-1952;
const gameCount = longestString(yearGroups)[1].length;
const padding = 0;
const blockWidth =
let blockWidth =
(width - marginLeft - marginRight) / yearN -
padding;
const blockHeight = blockWidth;
let blockHeight = blockWidth;
const height =
gameCount * (blockHeight + padding) +
marginBottom +
marginTop +
50;
const panelZoom = 1134/width;
const x = d3
.scaleLinear()
@ -152,12 +170,15 @@ function oddsPlot(d, {width} = {}) {
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; background-color: black;")
.on("mousemove", function(event, d)
{
//hideToolTip();
});
const extent = [[marginLeft, marginTop], [width - marginRight, height - marginTop]];
// svg.call(d3.zoom()
// .scaleExtent([1, 8])
// .translateExtent(extent)
// .extent(extent)
// .on("zoom", zoomed));
let yearInterval = yearN / 5;
@ -169,17 +190,16 @@ function oddsPlot(d, {width} = {}) {
yearInterval = yearN / 2;
}
svg
.append("g")
.attr("transform", `translate(0,${marginTop + blockHeight})`)
.call(
d3
.axisTop(x)
.ticks(yearInterval, "^c")
);
// Add the x-axis.
const xAxis = d3.axisTop(x).ticks(yearInterval, "^c");
const gx = svg.append("g")
.attr("transform", `translate(0,${marginTop + blockHeight})`)
.call(xAxis, x)
svg
.append("g")
.attr("class","blockPanel")
.selectAll()
.data(data)
.join("g")
@ -205,6 +225,22 @@ function oddsPlot(d, {width} = {}) {
hideToolTip();
});
// const zoom = d3.zoom()
// .scaleExtent([1, 32])
// .extent([[marginLeft, 0], [width - marginRight, height]])
// .translateExtent([[marginLeft, -Infinity], [width - marginRight, Infinity]])
// .on("zoom", zoomed);
// // When zooming, redraw the area and the x axis.
// function zoomed({ transform }) {
// //console.log(transform);
// d3.select(".blockPanel").attr("transform",transform)
// }
//svg.call(zoom.transform,new d3.ZoomTransform(panelZoom,1,1),[width,0]);
// zoom.scaleBy(svg.transition().duration(0), panelZoom);
// zoom.translateBy(svg.transition().duration(0), 10,10);
const infoBox = svg.append("g")
.attr("class", "info-box")
@ -276,21 +312,17 @@ function buildDualSquare(d, width, height) {
blockColor = neutFillSecondary;
}
}else{
if(d.sF > d.sU)//underdog covers, loses
{
winFill = winCircleFill;
}
if(d.sF - d.sU > d.spread)//favorite covers, wins
{
beatSpreadFill = coverCircleFill;
}
if(Math.abs(d.sF - d.sU) < d.spread && d.sF < d.sU)//underdog covers, wins
{
beatSpreadFill = coverCircleFill;
}
if(teamValue.indexOf(d.fav) > -1)
{
blockColor = favFill;
if(d.sF > d.sU)//favorite wins
{
winFill = winCircleFill;
}
if(d.sF - d.sU > d.spread)//favorite covers
{
beatSpreadFill = coverCircleFill;
}
}
if(teamValue.indexOf(d.und) > -1)
{
@ -298,13 +330,24 @@ function buildDualSquare(d, width, height) {
if(d.sU > d.sF)
{
winFill = winCircleFill;
beatSpreadFill = coverCircleFill;
}
if(Math.abs(d.sF - d.sU) < d.spread && d.sU < d.sF)//underdog loses, wins
{
beatSpreadFill = coverCircleFill;
}
}
}
if(d.spread==0)
{
blockColor = pkFill;
winFill = "none";
beatSpreadFill = "none";
}
}else{
if (d.pDiff) {
blockColor = d3.interpolateOrRd(diffColorScale(d.pDiff));
blockColor = d3.interpolatePiYG(diffColorScale(d.pDiff));
} else {
blockColor = noOUFill;
}
@ -333,15 +376,18 @@ function buildToolTip(d)
{
const weekString = d.week ? `Week ${d.week}` : "Playoffs";
const awayTeam = d.at ? d.und : `${d.fav} (-${d.spread})`;
const homeTeam = d.at ? `${d.fav} (-${d.spread})` : d.und;
const spreadText = d.spread > 0 ? `-${d.spread}` : `PK`;
const awayTeam = d.at ? d.und : `${d.fav} (${spreadText})`;
const homeTeam = d.at ? `${d.fav} (${spreadText})` : d.und;
const awayScore = d.at ? d.sU : d.sF;
const homeScore = d.at ? d.sF : d.sU;
const ou_text = d.ou ? `<div class="tip-item tip-text-full">OU ${d.ou}</div>` : ""
const toolTipHTML = `
<foreignObject width="400" height="200" x="15" y="15">
<div xmlns="http://www.w3.org/1999/xhtml" id="tool-tip-container" class="lexend-400">
<div class="tip-item-full">${formatDate(d.date)} (${weekString})</div>
<div class="tip-item tip-text-left">${d.sF}</div><div class="tip-item-small">&nbsp;-&nbsp;</div><div class="tip-item tip-text-right">${d.sU}</div>
<div class="tip-item tip-text-left">${awayScore}</div><div class="tip-item-small">&nbsp;-&nbsp;</div><div class="tip-item tip-text-right">${homeScore}</div>
<div class="tip-item tip-text-left">${awayTeam}</div><div class="tip-item-small">&nbsp;@&nbsp;</div><div class="tip-item tip-text-right">${homeTeam}</div>
${ou_text}
</div>
@ -382,9 +428,16 @@ function legend()
<td>Underdog</td>
<td>${isAll ? `` : `${legendSwatch(undFill)}`}</td>
<td>${isAll ? `${legendSwatch(neutFillSecondary)}` : `${legendSwatch(undFill,true,true)}`}</td>
<td>${isAll ? `${legendSwatch(neutFillSecondary)}` : `${legendSwatch(undFill,true, true)}`}</td>
<td>${isAll ? `${legendSwatch(neutFillSecondary)}` : `${legendSwatch(undFill,true,false)}`}</td>
<td>${isAll ? `${legendSwatch(neutFillSecondary,false,false,true)}` : `${legendSwatch(undFill,false,false,true)}`}</td>
</tr>
<tr>
<td>Pick'em</td>
<td>${legendSwatch(pkFill)}</td>
<td></td>
<td></td>
<td>${legendSwatch(pkFill,false,false,true)}</td>
</tr>
</tbody>
</table>
`
@ -395,8 +448,9 @@ function legend()
<svg width="100%" height="20" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="Gradient2" x1="0" x2="1" y1="0" y2="0">
<stop offset="0%" stop-color="${d3.interpolateOrRd(0)}"/>
<stop offset="100%" stop-color="${d3.interpolateOrRd(1)}"/>
<stop offset="0%" stop-color="${d3.interpolatePiYG(0)}"/>
<stop offset="50%" stop-color="${d3.interpolatePiYG(.5)}"/>
<stop offset="100%" stop-color="${d3.interpolatePiYG(1)}"/>
</linearGradient>
</defs>
@ -414,7 +468,7 @@ function legend()
${legendSwatch(noOUFill)} No O/U data
</td>
<td style="padding-top:5px">
${legendSwatch(d3.interpolateOrRd(1),false,false,true)} Playoffs
${legendSwatch(d3.interpolatePiYG(1),false,false,true)} Playoffs
</td>
</tbody>
@ -445,7 +499,17 @@ function legendSwatch(bgFill,cover,win,playoffs)
}
```
<h1 class="lexend-400">What Are the Odds?</h1>
<div class="grid grid-cols-1 lexend-200">
<div class="card" id="project-description">
<p>
This project came about as a coping mechanism. As an Eagles fan, I've spent the past two weeks after the NFC Championship thinking about how accurate predictions are when it comes to NFL game odds. I wanted to visualize how often odds-makers are accurate&#8202;&mdash;&#8202;and to what extent. I was able to find nearly every game's odds going back to 1952 thanks to <a href="https://www.sportsoddshistory.com/nfl-game-odds/" target="_blank">Sports Odds History</a>, used Python and <a href="https://www.crummy.com/software/BeautifulSoup/" target="_blank">BeautifulSoup</a> to parse the HTML into <a href="https://git.schaffz.in/gabi/NFLOddsVis/" target="_blank">json'ed data</a> (I'm working on mirroring to GitHub), and then <a href="https://d3js.org/" target="_blank">Observable D3</a> to build the graphs.
</p>
<p>
A mobile/responsive version of this is in the works and I plan to work on this to add more contextual layers, labels, and clarity. I'd love to hear your feedback on it, so please reach out on <a href="https://www.instagram.com/gabi.schaffz.in/" target="_blank">IG</a> or <a href="https://bsky.app/profile/gabi.schaffz.in" target="_blank">Bluesky</a>.
</p>
</div>
</div>
<div class="grid grid-cols-2">
<div class="card"><div>${teamSelect}</div><div style="padding-top:12px;">${viewSelect}</div></div>
<div class="card">${legend()}</div>
@ -534,7 +598,23 @@ function legendSwatch(bgFill,cover,win,playoffs)
padding-right: 8px;
}
#pred-score-qmark
{
cursor: pointer;
}
#pred-score-tooltip
{
display: none;
position: fixed;
background-color: black;
border: 2px white solid;
font-size: 12px;
line-height: 1.5;
padding: 8px;
max-width: 300px;
z-index: 100;
}
.lexend-400 {
font-family: "Lexend", serif;