Skip to content

Commit

Permalink
Create and deploy v1 of the website (#1)
Browse files Browse the repository at this point in the history
* Add all of the required files to create v1 of the website
  • Loading branch information
Currie32 authored Jul 10, 2023
1 parent 80bdc42 commit febe81d
Show file tree
Hide file tree
Showing 20 changed files with 1,502 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ docs/_build/
target/

# Jupyter Notebook
*.ipynb
.ipynb_checkpoints

# IPython
Expand Down
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Use the official Python base image
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /app

# Expose port 8080 for running the website
EXPOSE 8080

# Copy the required files
COPY app.py app.yaml requirements.txt robots.txt sitemap.xml ./
COPY assets assets
COPY pages pages

# Create a virtual environment and activate it
RUN python -m venv venv
ENV PATH="/app/venv/bin:$PATH"

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Specify the command to run the app
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0", "--port=8080"]
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Statistical Stories

[Statistical Stories](https://statisticalstories.xyz) is a website to help people learn about statistics, data science, and machine learning using simple stories and interactive visualizations.


## Testing
To deploy a local version of the website:

```
# Create a virtual environment
python3 -m venv venv
# Install all of the dependencies
pip3 install -r requirements.txt
# Run the website locally
python3 app.py
```

## Improvements
Feel free to create a github issue or send me an email at [email protected] if you see anything wrong with the website or would like me to add/change something.
191 changes: 191 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
from typing import Dict, List, Tuple

import dash_bootstrap_components as dbc
from dash import Dash, dcc, html, no_update, page_container
from dash.dependencies import Input, Output, State
from flask import send_from_directory


app = Dash(
__name__,
use_pages=True,
pages_folder="pages",
external_stylesheets=[dbc.icons.BOOTSTRAP, dbc.themes.BOOTSTRAP]
)
app.config.suppress_callback_exceptions = True
server = app.server

@server.route('/robots.txt')
def serve_robots():
return send_from_directory('.', 'robots.txt', mimetype='text/plain')


@server.route('/sitemap.xml')
def serve_sitemap():
return send_from_directory('.', 'sitemap.xml', mimetype='application/xml')


app.layout = html.Div([
html.Div(className='container', children=[
html.Div(id='navbar', children=[
html.Div(className='button-menu-and-title', children=[
# className "bi bi-list" is required for the menu icon
# className "menu-hidden" is replaced by "menu-visible" depending on
# the screen size and interactions with the website
html.Button(html.I(className="bi bi-list"), id="button-navbar", className='menu-hidden'),
html.H1(
className='navbar-title',
children=dcc.Link("Statistical Stories", href="/", id="link-home"),
),
]),
# "display: none" is also replaced depending on screen size and interactions
html.Div(id='navbar-menus', className='menu-hidden', style={'display': 'none'}, children=[
html.Div([
dcc.Input(id='search-bar', type='text', placeholder='Search...'),
html.Div(id='search-results', style={'padding': '10px'})
]),
dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem(dcc.Link("Normal Distribution", href="/normal")),
dbc.DropdownMenuItem(dcc.Link("Poisson Distribution", href="/poisson")),
],
nav=True,
in_navbar=True,
label="Distributions",
),
dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem(dcc.Link("K-Nearest Neighbors", href="/k-nearest-neighbors")),
],
nav=True,
in_navbar=True,
label="Statistical Models",
),
dbc.DropdownMenu(
children=[
dbc.DropdownMenuItem(dcc.Link("ANOVA", href="/anova")),
],
nav=True,
in_navbar=True,
label="Statistical Tests",
),
]),
]),
html.Div(id='content-body-wrapper', children=[
dcc.Store(id="screen-width-store"),
dcc.Location(id='url', refresh=False),
page_container,
html.Div(id='footer', children=[
html.P("Statistical Stories. All rights reserved."),
html.P(className="footer-pipe", children=["|"]),
html.A("We're open source!", target="_blank", href="https://github.com/Currie32/statistical_stories")
]),
]),
])
])


# Get the width of the screen when the website loads
app.clientside_callback(
"""
function() {return window.innerWidth}
""",
Output("screen-width-store", "data"),
Input('button-navbar', 'n_clicks'),
)


@app.callback(
Output('navbar-menus', 'className'),
Output('navbar-menus', 'style'),
Output('button-navbar', 'className'),
Output('content-body-wrapper', 'className'),
Input('url', 'pathname'),
Input('button-navbar', 'n_clicks'),
Input('screen-width-store', 'data'),
State('navbar-menus', 'className'),
State('button-navbar', 'className'),
State('content-body-wrapper', 'className'),
)
def display_navbar_menu_toggle(
pathname: str, # Not used but required input to close menu
n_clicks: int,
screen_width: int,
menu_classname: str,
button_class_name: str,
content_class_name: str,
) -> Tuple[str, Dict[str, str], str, str]:
"""
Logic of whether to display the dropdown navbar menu.
"""

if screen_width < 800 and not n_clicks:
return 'menu-hidden', {'display': 'none'}, 'menu-hidden', 'menu-hidden'
elif menu_classname == 'menu-visible' and n_clicks:
return 'menu-hidden', {'display': 'none'}, 'menu-hidden', 'menu-hidden'
elif button_class_name == 'menu-hidden' and n_clicks:
return 'menu-visible', {'display': 'block'}, 'menu-visible', 'menu-visible'
elif content_class_name == 'menu-hidden':
return 'menu-hidden', {'display': 'none'}, 'menu-hidden', 'menu-hidden'
else:
return 'menu-visible', {'display': 'block'}, 'menu-visible', 'menu-visible'


def read_page_content(page_file: str) -> str:
"""
Load and read the contents of a web page.
"""
with open(page_file, 'r') as f:
content = f.read()
return content

@app.callback(
Output('search-results', 'children'),
Output('search-results', 'style'),
Output('url', 'pathname'),
[Input('search-bar', 'value')],
[State('url', 'pathname')],
)
def search_pages(
search_query: str,
current_page: str
) -> Tuple[List[dcc.Link], Dict[str, str], str]:
"""
Return a list of pages that contain the search query.
Update the current_page if a link to a different page is clicked on.
Update the padding of the search-results div based on the number of results.
"""

# Don't display the search-results div if there is no search query
if not search_query:
return None, {'padding': '0px'}, current_page

# Pages of the website that can be searched
pages = [
{'title': 'Normal distribution', 'path': 'pages/distribution_normal.py', 'url': '/normal'},
{'title': 'Poisson distribution', 'path': 'pages/distribution_poisson.py', 'url': '/poisson'},
{'title': 'k-Nearest Neighbors', 'path': 'pages/model_k_nearest_neighbors.py', 'url': '/k-nearest-neighbors'},
{'title': 'ANOVA', 'path': 'pages/test_anova.py', 'url': 'anova'},
]

# Append pages that match the search query
matching_pages = []
for page in pages:
content = read_page_content(page['path'])
if search_query.lower() in content.lower():
matching_pages.append(page)

# If there are matching pages, display them
if matching_pages:
result_list = []
for page in matching_pages:
result_list.append(dcc.Link(page['title'], href=page['url']))
return result_list, {'padding': '10px'}, no_update

# If no matching pages are found, display a message
else:
return html.P('No matching pages found.'), {'padding': '10px 10px 1px'}, no_update


if __name__ == '__main__':
app.run_server(debug=True, port=8080)
16 changes: 16 additions & 0 deletions app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
service: default
runtime: python39

basic_scaling:
max_instances: 2
idle_timeout: 10m

resources:
cpu: 1
memory_gb: 1
disk_size_gb: 1

env_variables:
PORT: 8080

entrypoint: gunicorn -b :$PORT main:server
35 changes: 35 additions & 0 deletions assets/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
html {
margin: 0px;
}
body {
margin: 0px;
font-family: Georgia, 'Times New Roman', Times, serif;
overflow-x: hidden;
}
p {
line-height: 22px;
}
a {
color: black;
}
li {
margin-bottom: 5px;
}
pre {
background-color: #f5f5f5;
font-size: 16px;
padding: 0px 25px;
white-space: pre-wrap;
}
.container {
display: relative;
}
#content-body-wrapper {
justify-content: center;
margin-left: 300px;
}
@media (max-width: 800px) {
#content-body-wrapper {
margin-left: 0px
}
}
Loading

0 comments on commit febe81d

Please sign in to comment.