diff --git a/apps/dash-wind-streaming/.gitignore b/apps/dash-wind-streaming/.gitignore index f465a23fa..7cabaa742 100644 --- a/apps/dash-wind-streaming/.gitignore +++ b/apps/dash-wind-streaming/.gitignore @@ -1,5 +1,145 @@ -venv -*.pyc -.DS_Store +# .gitignore specifies the files that shouldn't be included +# in version control and therefore shouldn't be included when +# deploying an application to Dash Enterprise +# This is a very exhaustive list! +# This list was based off of https://github.com/github/gitignore +# Ignore data that is generated during the runtime of an application +# This folder is used by the "Large Data" sample applications +runtime_data/ + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +# Jupyter Notebook +.ipynb_checkpoints +*/.ipynb_checkpoints/* +# IPython +profile_default/ +ipython_config.py +# Environments .env -settings.json \ No newline at end of file +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +# Spyder project settings +.spyderproject +.spyproject +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal +# macOS General +.DS_Store +.AppleDouble +.LSOverride +# Icon must end with two \r +Icon +# Thumbnails +._* +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +# Dump file +*.stackdump +# Folder config file +[Dd]esktop.ini +# Recycle Bin used on file shares +$RECYCLE.BIN/ +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp +# Windows shortcuts +*.lnk +# History files +.Rhistory +.Rapp.history +# Session Data files +.RData +# User-specific files +.Ruserdata +# Example code in package build process +*-Ex.R +# Output files from R CMD check +/*.Rcheck/ +# RStudio files +.Rproj.user/ +# produced vignettes +vignettes/*.html +vignettes/*.pdf +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth +# knitr and R markdown default cache directories +*_cache/ +/cache/ +# Temporary files created by R markdown +*.utf8.md +*.knit.md +# R Environment Variables +.Renviron +# Linux +*~ +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* +# KDE directory preferences +.directory +# Linux trash folder which might appear on any partition or disk +.Trash-* +# .nfs files are created when an open file is removed but is still being accessed +.nfs* +# VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace +# SublineText +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +# Workspace files are user-specific +*.sublime-workspace +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project +# SFTP configuration file +sftp-config.json +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings \ No newline at end of file diff --git a/apps/dash-wind-streaming/Notebook/wind-speed-generation.ipynb b/apps/dash-wind-streaming/Notebook/wind-speed-generation.ipynb deleted file mode 100644 index 1976288ed..000000000 --- a/apps/dash-wind-streaming/Notebook/wind-speed-generation.ipynb +++ /dev/null @@ -1,664 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "import plotly.plotly as py\n", - "from plotly.graph_objs import *\n", - "from scipy.optimize import curve_fit\n", - "import numpy as np\n", - "import pandas as pd\n", - "from scipy.stats import norm\n", - "import plotly.figure_factory as ff\n", - "import math\n", - "from scipy.stats import skewnorm\n", - "import datetime as dt" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "windVal = []\n", - "windError = []\n", - "windOrientation = []\n", - "prevVal = 20\n", - "prevOrientation = np.random.uniform(0, 360)\n", - "for i in range(0, 86400):\n", - " windVal.append(abs(np.random.normal(prevVal, 2, 1)[0]))\n", - " windError.append(abs(np.random.normal(round(prevVal/10), 1)))\n", - " if(i % 100 == 0):\n", - " windOrientation.append(np.random.uniform(prevOrientation-50,\n", - " prevOrientation+50))\n", - " else:\n", - " windOrientation.append(np.random.uniform(prevOrientation-5,\n", - " prevOrientation+5))\n", - " if(round(windVal[-1]) > 45):\n", - " prevVal = int(math.floor(windVal[-1]))\n", - " elif(round(windVal[-1]) < 10):\n", - " prevVal = int(math.ceil(windVal[-1]))\n", - " else:\n", - " prevVal = int(round(windVal[-1]))\n", - " prevOrientation = windOrientation[-1]" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "df = pd.DataFrame.from_dict({\n", - " 'Speed': windVal,\n", - " 'SpeedError': windError,\n", - " 'Direction': windOrientation\n", - " })" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "now = dt.datetime.now()\n", - "sec = now.second\n", - "minute = now.minute\n", - "hour = now.hour\n", - "\n", - "totalTime = (hour * 3600) + (minute * 60) + (sec)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "import sqlite3\n", - "from datetime import *\n", - "connex = sqlite3.connect(\"wind-data.db\") # Opens file if exists, else creates file\n", - "cur = connex.cursor() " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "df.to_sql(name='Wind', con=connex)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
indexDirectionSpeedSpeedError
051818388.46711323.4556032.098050
151819385.24623022.8227481.073134
251820388.45413721.5370131.982093
351821386.65364225.4226322.680533
451822387.92431226.4440593.757128
551823392.38467824.8497311.705354
651824388.82520127.2553391.626143
751825384.11482025.5042711.971804
851826384.64613723.1418192.417021
951827387.90954723.2087111.300878
1051828388.01344419.4688761.846851
1151829391.43136316.0965081.756832
1251830395.27629813.4340310.085637
1351831391.45361614.4723170.229909
1451832394.9331619.3550900.369801
1551833395.1968926.2853181.750981
1651834396.4108484.8118511.339742
1751835399.5577622.8578930.961948
1851836394.9457873.8048831.296398
1951837396.6976273.6378590.559455
2051838397.1164282.7254070.465070
2151839395.5539733.2544781.126300
2251840395.3574686.2782690.242901
2351841399.3091596.2179411.110519
2451842396.16715411.5749180.472902
2551843392.94636912.5467612.696686
2651844388.65189011.5677950.000096
2751845389.26229812.5732601.361332
2851846389.44056315.2195820.180667
2951847390.46085813.7649022.724583
...............
16951987423.50499811.7255220.794980
17051988423.84520313.9053360.421338
17151989423.17882312.3249261.188691
17251990425.20652711.2511390.113524
17351991423.73781712.6404402.880048
17451992418.89845616.5504661.918532
17551993417.23887217.7937840.551114
17651994415.24113218.0972230.654216
17751995417.11002016.4295102.152254
17851996421.54961612.3424330.132018
17951997422.6924048.8556901.373699
18051998419.3232634.1367340.133812
18151999420.6514887.7816541.317008
18252000383.2832488.4082711.286599
18352001378.4552399.0317570.560556
18452002376.46420011.9434320.222717
18552003376.3903069.9693830.134329
18652004380.20182714.7367791.031438
18752005382.74616314.6924221.896312
18852006386.20154114.6106902.840782
18952007385.19131216.2168571.175926
19052008385.68991316.2727500.249718
19152009383.08084516.0211070.289782
19252010385.67501013.6302530.994068
19352011382.75925618.1450362.438731
19452012383.77383517.2613192.498423
19552013385.41400122.5965762.913657
19652014383.93096625.3886471.465414
19752015386.89887623.4120012.190445
19852016384.85767723.8839221.601534
\n", - "

199 rows × 4 columns

\n", - "
" - ], - "text/plain": [ - " index Direction Speed SpeedError\n", - "0 51818 388.467113 23.455603 2.098050\n", - "1 51819 385.246230 22.822748 1.073134\n", - "2 51820 388.454137 21.537013 1.982093\n", - "3 51821 386.653642 25.422632 2.680533\n", - "4 51822 387.924312 26.444059 3.757128\n", - "5 51823 392.384678 24.849731 1.705354\n", - "6 51824 388.825201 27.255339 1.626143\n", - "7 51825 384.114820 25.504271 1.971804\n", - "8 51826 384.646137 23.141819 2.417021\n", - "9 51827 387.909547 23.208711 1.300878\n", - "10 51828 388.013444 19.468876 1.846851\n", - "11 51829 391.431363 16.096508 1.756832\n", - "12 51830 395.276298 13.434031 0.085637\n", - "13 51831 391.453616 14.472317 0.229909\n", - "14 51832 394.933161 9.355090 0.369801\n", - "15 51833 395.196892 6.285318 1.750981\n", - "16 51834 396.410848 4.811851 1.339742\n", - "17 51835 399.557762 2.857893 0.961948\n", - "18 51836 394.945787 3.804883 1.296398\n", - "19 51837 396.697627 3.637859 0.559455\n", - "20 51838 397.116428 2.725407 0.465070\n", - "21 51839 395.553973 3.254478 1.126300\n", - "22 51840 395.357468 6.278269 0.242901\n", - "23 51841 399.309159 6.217941 1.110519\n", - "24 51842 396.167154 11.574918 0.472902\n", - "25 51843 392.946369 12.546761 2.696686\n", - "26 51844 388.651890 11.567795 0.000096\n", - "27 51845 389.262298 12.573260 1.361332\n", - "28 51846 389.440563 15.219582 0.180667\n", - "29 51847 390.460858 13.764902 2.724583\n", - ".. ... ... ... ...\n", - "169 51987 423.504998 11.725522 0.794980\n", - "170 51988 423.845203 13.905336 0.421338\n", - "171 51989 423.178823 12.324926 1.188691\n", - "172 51990 425.206527 11.251139 0.113524\n", - "173 51991 423.737817 12.640440 2.880048\n", - "174 51992 418.898456 16.550466 1.918532\n", - "175 51993 417.238872 17.793784 0.551114\n", - "176 51994 415.241132 18.097223 0.654216\n", - "177 51995 417.110020 16.429510 2.152254\n", - "178 51996 421.549616 12.342433 0.132018\n", - "179 51997 422.692404 8.855690 1.373699\n", - "180 51998 419.323263 4.136734 0.133812\n", - "181 51999 420.651488 7.781654 1.317008\n", - "182 52000 383.283248 8.408271 1.286599\n", - "183 52001 378.455239 9.031757 0.560556\n", - "184 52002 376.464200 11.943432 0.222717\n", - "185 52003 376.390306 9.969383 0.134329\n", - "186 52004 380.201827 14.736779 1.031438\n", - "187 52005 382.746163 14.692422 1.896312\n", - "188 52006 386.201541 14.610690 2.840782\n", - "189 52007 385.191312 16.216857 1.175926\n", - "190 52008 385.689913 16.272750 0.249718\n", - "191 52009 383.080845 16.021107 0.289782\n", - "192 52010 385.675010 13.630253 0.994068\n", - "193 52011 382.759256 18.145036 2.438731\n", - "194 52012 383.773835 17.261319 2.498423\n", - "195 52013 385.414001 22.596576 2.913657\n", - "196 52014 383.930966 25.388647 1.465414\n", - "197 52015 386.898876 23.412001 2.190445\n", - "198 52016 384.857677 23.883922 1.601534\n", - "\n", - "[199 rows x 4 columns]" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "con = sqlite3.connect(\"wind-data.db\")\n", - "df = pd.read_sql_query(\"SELECT * from Wind where rowid > \"+ str(totalTime-200) + \" AND rowid < \" + str(totalTime) + \";\" , con)\n", - "df" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 2", - "language": "python", - "name": "python2" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/apps/dash-wind-streaming/README.md b/apps/dash-wind-streaming/README.md index 58a450925..7dfb6d6ac 100644 --- a/apps/dash-wind-streaming/README.md +++ b/apps/dash-wind-streaming/README.md @@ -37,7 +37,7 @@ Open a browser at http://127.0.0.1:8050 ## Screenshots -![demo.gif](demo.gif) +![demo.gif](assets/github/demo.gif) ## Resources diff --git a/apps/dash-wind-streaming/__init__.py b/apps/dash-wind-streaming/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/dash-wind-streaming/app.py b/apps/dash-wind-streaming/app.py index 37e9d481b..e7ff083e1 100644 --- a/apps/dash-wind-streaming/app.py +++ b/apps/dash-wind-streaming/app.py @@ -1,472 +1,66 @@ -import os -import pathlib -import numpy as np -import datetime as dt -import dash -import dash_core_components as dcc -import dash_html_components as html +from dash import Dash, html, dcc, Input, Output, State, callback, callback_context -from dash.exceptions import PreventUpdate -from dash.dependencies import Input, Output, State -from scipy.stats import rayleigh -from db.api import get_wind_data, get_wind_data_by_id - - -GRAPH_INTERVAL = os.environ.get("GRAPH_INTERVAL", 5000) - -app = dash.Dash( - __name__, - meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}], -) -app.title = "Wind Speed Dashboard" +from constants import GRAPH_INTERVAL +from utils.components import Header, wind_speed_card, histogram_card, wind_direction_card +import utils.figures as figs +app = Dash(__name__, title="Wind Speed Dashboard") server = app.server -app_color = {"graph_bg": "#082255", "graph_line": "#007ACE"} - app.layout = html.Div( [ - # header - html.Div( - [ - html.Div( - [ - html.H4("WIND SPEED STREAMING", className="app__header__title"), - html.P( - "This app continually queries a SQL database and displays live charts of wind speed and wind direction.", - className="app__header__title--grey", - ), - ], - className="app__header__desc", - ), - html.Div( - [ - html.A( - html.Button("SOURCE CODE", className="link-button"), - href="https://github.com/plotly/dash-sample-apps/tree/main/apps/dash-wind-streaming", - ), - html.A( - html.Button("ENTERPRISE DEMO", className="link-button"), - href="https://plotly.com/get-demo/", - ), - html.A( - html.Img( - src=app.get_asset_url("dash-new-logo.png"), - className="app__menu__img", - ), - href="https://plotly.com/dash/", - ), - ], - className="app__header__logo", - ), - ], - className="app__header", + Header( + app, + "WIND SPEED STREAMING", + "This app continually queries a SQL database and displays live charts of wind speed and wind direction.", ), html.Div( [ - # wind speed - html.Div( - [ - html.Div( - [html.H6("WIND SPEED (MPH)", className="graph__title")] - ), - dcc.Graph( - id="wind-speed", - figure=dict( - layout=dict( - plot_bgcolor=app_color["graph_bg"], - paper_bgcolor=app_color["graph_bg"], - ) - ), - ), - dcc.Interval( - id="wind-speed-update", - interval=int(GRAPH_INTERVAL), - n_intervals=0, - ), - ], - className="two-thirds column wind__speed__container", - ), + wind_speed_card("wind-speed"), html.Div( [ - # histogram - html.Div( - [ - html.Div( - [ - html.H6( - "WIND SPEED HISTOGRAM", - className="graph__title", - ) - ] - ), - html.Div( - [ - dcc.Slider( - id="bin-slider", - min=1, - max=60, - step=1, - value=20, - updatemode="drag", - marks={ - 20: {"label": "20"}, - 40: {"label": "40"}, - 60: {"label": "60"}, - }, - ) - ], - className="slider", - ), - html.Div( - [ - dcc.Checklist( - id="bin-auto", - options=[ - {"label": "Auto", "value": "Auto"} - ], - value=["Auto"], - inputClassName="auto__checkbox", - labelClassName="auto__label", - ), - html.P( - "# of Bins: Auto", - id="bin-size", - className="auto__p", - ), - ], - className="auto__container", - ), - dcc.Graph( - id="wind-histogram", - figure=dict( - layout=dict( - plot_bgcolor=app_color["graph_bg"], - paper_bgcolor=app_color["graph_bg"], - ) - ), - ), - ], - className="graph__container first", - ), - # wind direction - html.Div( - [ - html.Div( - [ - html.H6( - "WIND DIRECTION", className="graph__title" - ) - ] - ), - dcc.Graph( - id="wind-direction", - figure=dict( - layout=dict( - plot_bgcolor=app_color["graph_bg"], - paper_bgcolor=app_color["graph_bg"], - ) - ), - ), - ], - className="graph__container second", - ), + histogram_card("wind-histogram"), + wind_direction_card("wind-direction") ], className="one-third column histogram__direction", ), ], className="app__content", ), + dcc.Interval(id="wind-speed-update", interval=GRAPH_INTERVAL) ], className="app__container", ) -def get_current_time(): - """ Helper function to get the current time in seconds. """ - - now = dt.datetime.now() - total_time = (now.hour * 3600) + (now.minute * 60) + (now.second) - return total_time - - -@app.callback( - Output("wind-speed", "figure"), [Input("wind-speed-update", "n_intervals")] -) -def gen_wind_speed(interval): - """ - Generate the wind speed graph. - - :params interval: update the graph based on an interval - """ - - total_time = get_current_time() - df = get_wind_data(total_time - 200, total_time) - - trace = dict( - type="scatter", - y=df["Speed"], - line={"color": "#42C4F7"}, - hoverinfo="skip", - error_y={ - "type": "data", - "array": df["SpeedError"], - "thickness": 1.5, - "width": 2, - "color": "#B4E8FC", - }, - mode="lines", - ) - - layout = dict( - plot_bgcolor=app_color["graph_bg"], - paper_bgcolor=app_color["graph_bg"], - font={"color": "#fff"}, - height=700, - xaxis={ - "range": [0, 200], - "showline": True, - "zeroline": False, - "fixedrange": True, - "tickvals": [0, 50, 100, 150, 200], - "ticktext": ["200", "150", "100", "50", "0"], - "title": "Time Elapsed (sec)", - }, - yaxis={ - "range": [ - min(0, min(df["Speed"])), - max(45, max(df["Speed"]) + max(df["SpeedError"])), - ], - "showgrid": True, - "showline": True, - "fixedrange": True, - "zeroline": False, - "gridcolor": app_color["graph_line"], - "nticks": max(6, round(df["Speed"].iloc[-1] / 10)), - }, - ) - - return dict(data=[trace], layout=layout) - - -@app.callback( - Output("wind-direction", "figure"), [Input("wind-speed-update", "n_intervals")] -) -def gen_wind_direction(interval): - """ - Generate the wind direction graph. - - :params interval: update the graph based on an interval - """ - - total_time = get_current_time() - df = get_wind_data_by_id(total_time) - val = df["Speed"].iloc[-1] - direction = [0, (df["Direction"][0] - 20), (df["Direction"][0] + 20), 0] - - traces_scatterpolar = [ - {"r": [0, val, val, 0], "fillcolor": "#084E8A"}, - {"r": [0, val * 0.65, val * 0.65, 0], "fillcolor": "#B4E1FA"}, - {"r": [0, val * 0.3, val * 0.3, 0], "fillcolor": "#EBF5FA"}, - ] - - data = [ - dict( - type="scatterpolar", - r=traces["r"], - theta=direction, - mode="lines", - fill="toself", - fillcolor=traces["fillcolor"], - line={"color": "rgba(32, 32, 32, .6)", "width": 1}, - ) - for traces in traces_scatterpolar - ] - - layout = dict( - height=350, - plot_bgcolor=app_color["graph_bg"], - paper_bgcolor=app_color["graph_bg"], - font={"color": "#fff"}, - autosize=False, - polar={ - "bgcolor": app_color["graph_line"], - "radialaxis": {"range": [0, 45], "angle": 45, "dtick": 10}, - "angularaxis": {"showline": False, "tickcolor": "white"}, - }, - showlegend=False, - ) - - return dict(data=data, layout=layout) - - -@app.callback( +@callback( Output("wind-histogram", "figure"), - [Input("wind-speed-update", "n_intervals")], - [ - State("wind-speed", "figure"), - State("bin-slider", "value"), - State("bin-auto", "value"), - ], + Output("wind-speed", "figure"), + Output("wind-direction", "figure"), + Input("wind-speed-update", "n_intervals"), + State("bin-slider", "value"), + State("bin-auto", "value"), ) -def gen_wind_histogram(interval, wind_speed_figure, slider_value, auto_state): - """ - Genererate wind histogram graph. - - :params interval: upadte the graph based on an interval - :params wind_speed_figure: current wind speed graph - :params slider_value: current slider value - :params auto_state: current auto state - """ - - wind_val = [] +def return_gen_wind_histogram(interval, slider_value, auto_state): + fig_wind_speed = figs.gen_wind_speed() + fig_wind_direction =figs.gen_wind_direction() + fig_wind_histogram = figs.gen_wind_histogram(fig_wind_speed, slider_value, auto_state) + return fig_wind_histogram, fig_wind_speed, fig_wind_direction - try: - # Check to see whether wind-speed has been plotted yet - if wind_speed_figure is not None: - wind_val = wind_speed_figure["data"][0]["y"] - if "Auto" in auto_state: - bin_val = np.histogram( - wind_val, - bins=range(int(round(min(wind_val))), int(round(max(wind_val)))), - ) - else: - bin_val = np.histogram(wind_val, bins=slider_value) - except Exception as error: - raise PreventUpdate - avg_val = float(sum(wind_val)) / len(wind_val) - median_val = np.median(wind_val) - - pdf_fitted = rayleigh.pdf( - bin_val[1], loc=(avg_val) * 0.55, scale=(bin_val[1][-1] - bin_val[1][0]) / 3 - ) - - y_val = (pdf_fitted * max(bin_val[0]) * 20,) - y_val_max = max(y_val[0]) - bin_val_max = max(bin_val[0]) - - trace = dict( - type="bar", - x=bin_val[1], - y=bin_val[0], - marker={"color": app_color["graph_line"]}, - showlegend=False, - hoverinfo="x+y", - ) - - traces_scatter = [ - {"line_dash": "dash", "line_color": "#2E5266", "name": "Average"}, - {"line_dash": "dot", "line_color": "#BD9391", "name": "Median"}, - ] - - scatter_data = [ - dict( - type="scatter", - x=[bin_val[int(len(bin_val) / 2)]], - y=[0], - mode="lines", - line={"dash": traces["line_dash"], "color": traces["line_color"]}, - marker={"opacity": 0}, - visible=True, - name=traces["name"], - ) - for traces in traces_scatter - ] - - trace3 = dict( - type="scatter", - mode="lines", - line={"color": "#42C4F7"}, - y=y_val[0], - x=bin_val[1][: len(bin_val[1])], - name="Rayleigh Fit", - ) - layout = dict( - height=350, - plot_bgcolor=app_color["graph_bg"], - paper_bgcolor=app_color["graph_bg"], - font={"color": "#fff"}, - xaxis={ - "title": "Wind Speed (mph)", - "showgrid": False, - "showline": False, - "fixedrange": True, - }, - yaxis={ - "showgrid": False, - "showline": False, - "zeroline": False, - "title": "Number of Samples", - "fixedrange": True, - }, - autosize=True, - bargap=0.01, - bargroupgap=0, - hovermode="closest", - legend={ - "orientation": "h", - "yanchor": "bottom", - "xanchor": "center", - "y": 1, - "x": 0.5, - }, - shapes=[ - { - "xref": "x", - "yref": "y", - "y1": int(max(bin_val_max, y_val_max)) + 0.5, - "y0": 0, - "x0": avg_val, - "x1": avg_val, - "type": "line", - "line": {"dash": "dash", "color": "#2E5266", "width": 5}, - }, - { - "xref": "x", - "yref": "y", - "y1": int(max(bin_val_max, y_val_max)) + 0.5, - "y0": 0, - "x0": median_val, - "x1": median_val, - "type": "line", - "line": {"dash": "dot", "color": "#BD9391", "width": 5}, - }, - ], - ) - return dict(data=[trace, scatter_data[0], scatter_data[1], trace3], layout=layout) - - -@app.callback( +@callback( Output("bin-auto", "value"), - [Input("bin-slider", "value")], - [State("wind-speed", "figure")], -) -def deselect_auto(slider_value, wind_speed_figure): - """ Toggle the auto checkbox. """ - - # prevent update if graph has no data - if "data" not in wind_speed_figure: - raise PreventUpdate - if not len(wind_speed_figure["data"]): - raise PreventUpdate - - if wind_speed_figure is not None and len(wind_speed_figure["data"][0]["y"]) > 5: - return [""] - return ["Auto"] - - -@app.callback( Output("bin-size", "children"), - [Input("bin-auto", "value")], - [State("bin-slider", "value")], + Input("bin-slider", "value"), + Input("bin-auto", "value"), + prevent_initial_call=True, ) -def show_num_bins(autoValue, slider_value): - """ Display the number of bins. """ - - if "Auto" in autoValue: - return "# of Bins: Auto" - return "# of Bins: " + str(int(slider_value)) +def deselect_auto(slider_value, bin_auto): + triggered_id = callback_context.triggered[0]["prop_id"].split(".")[0] + if triggered_id == "bin-slider": + return [], "# of Bins: " + str(int(slider_value)) + else: + return ["Auto"], "# of Bins: Auto" if __name__ == "__main__": diff --git a/apps/dash-wind-streaming/assets/app.css b/apps/dash-wind-streaming/assets/css/app.css similarity index 100% rename from apps/dash-wind-streaming/assets/app.css rename to apps/dash-wind-streaming/assets/css/app.css diff --git a/apps/dash-wind-streaming/assets/css/header.css b/apps/dash-wind-streaming/assets/css/header.css new file mode 100644 index 000000000..ec39e0172 --- /dev/null +++ b/apps/dash-wind-streaming/assets/css/header.css @@ -0,0 +1,65 @@ +/* Header */ +.header { + height: 10vh; + display: flex; + background-color: transparent; + padding-left: 2%; + padding-right: 2%; + font-family: playfair display, sans-serif; +} + +.header .header-title { + color: #FFFFFF !important; + font-size: 5vh; +} + +.header-logos { + margin-left: auto; + align-self: center; +} + +.header-logos img { + margin-left: 3vh !important; + max-height: 5vh; +} + +.subheader-title { + font-size: 1.5vh; + color: #A2ACB8 !important; +} + + +/* Demo button css */ +.demo-button { + font-size: 1.5vh; + font-family: Open Sans, sans-serif; + text-decoration: none; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + border-radius: 8px; + font-weight: 700; + -webkit-padding-start: 1rem; + padding-inline-start: 1rem; + -webkit-padding-end: 1rem; + padding-inline-end: 1rem; + color: #ffffff; + letter-spacing: 1.5px; + border: solid 1.5px transparent; + box-shadow: 2px 1000px 1px #0c0c0c inset; + background-image: linear-gradient(135deg, #7A76FF, #7A76FF, #7FE4FF); + -webkit-background-size: 200% 100%; + background-size: 200% 100%; + -webkit-background-position: 99%; + background-position: 99%; + background-origin: border-box; + transition: all .4s ease-in-out; + padding-top: 1vh; + padding-bottom: 1vh; +} + +.demo-button:hover { + color: #7A76FF; + background-position: 0%; +} \ No newline at end of file diff --git a/apps/dash-wind-streaming/assets/style.css b/apps/dash-wind-streaming/assets/css/style.css similarity index 100% rename from apps/dash-wind-streaming/assets/style.css rename to apps/dash-wind-streaming/assets/css/style.css diff --git a/apps/dash-wind-streaming/assets/dash-new-logo.png b/apps/dash-wind-streaming/assets/dash-new-logo.png deleted file mode 100644 index 040cde174..000000000 Binary files a/apps/dash-wind-streaming/assets/dash-new-logo.png and /dev/null differ diff --git a/apps/dash-wind-streaming/assets/demo-button.css b/apps/dash-wind-streaming/assets/demo-button.css deleted file mode 100644 index f030e1b9e..000000000 --- a/apps/dash-wind-streaming/assets/demo-button.css +++ /dev/null @@ -1,6 +0,0 @@ -.link-button { - margin-top: 10px; - margin-right: 10px; - vertical-align: top; - color: white; -} \ No newline at end of file diff --git a/apps/dash-wind-streaming/demo.gif b/apps/dash-wind-streaming/assets/github/demo.gif similarity index 100% rename from apps/dash-wind-streaming/demo.gif rename to apps/dash-wind-streaming/assets/github/demo.gif diff --git a/apps/dash-wind-streaming/assets/images/plotly-logo.png b/apps/dash-wind-streaming/assets/images/plotly-logo.png new file mode 100644 index 000000000..984dd57ab Binary files /dev/null and b/apps/dash-wind-streaming/assets/images/plotly-logo.png differ diff --git a/apps/dash-wind-streaming/constants.py b/apps/dash-wind-streaming/constants.py new file mode 100644 index 000000000..608ffde50 --- /dev/null +++ b/apps/dash-wind-streaming/constants.py @@ -0,0 +1,7 @@ +import os + +app_color = {"graph_bg": "#082255", "graph_line": "#007ACE"} + +DB_FILE = "data/wind-data.db" + +GRAPH_INTERVAL = int(os.environ.get("GRAPH_INTERVAL", 5000)) \ No newline at end of file diff --git a/apps/dash-wind-streaming/db/wind-data.db b/apps/dash-wind-streaming/data/wind-data.db similarity index 100% rename from apps/dash-wind-streaming/db/wind-data.db rename to apps/dash-wind-streaming/data/wind-data.db diff --git a/apps/dash-wind-streaming/db/__init__.py b/apps/dash-wind-streaming/db/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/dash-wind-streaming/requirements.txt b/apps/dash-wind-streaming/requirements.txt index c34ae2487..da7dcd54e 100644 --- a/apps/dash-wind-streaming/requirements.txt +++ b/apps/dash-wind-streaming/requirements.txt @@ -1,5 +1,4 @@ -dash==1.0.0 -numpy==1.16.4 -pandas==0.24.2 -scipy==1.3.0 -gunicorn==19.9.0 \ No newline at end of file +dash==2.4.1 +pandas==1.4.2 +gunicorn==20.1.0 +scipy==1.8.1 \ No newline at end of file diff --git a/apps/dash-wind-streaming/runtime.txt b/apps/dash-wind-streaming/runtime.txt new file mode 100644 index 000000000..cfa660c42 --- /dev/null +++ b/apps/dash-wind-streaming/runtime.txt @@ -0,0 +1 @@ +python-3.8.0 \ No newline at end of file diff --git a/apps/dash-wind-streaming/utils/components.py b/apps/dash-wind-streaming/utils/components.py new file mode 100644 index 000000000..cc20688a8 --- /dev/null +++ b/apps/dash-wind-streaming/utils/components.py @@ -0,0 +1,99 @@ +from dash import html, dcc + +from constants import app_color + +def Header(app, header, subheader=None): + left_headers = html.Div( + [ + html.Div(header, className="header-title"), + html.Div(subheader, className="subheader-title"), + ] + ) + + logo = html.Img(src=app.get_asset_url("images/plotly-logo.png")) + link = html.A(logo, href="https://plotly.com/dash/", target="_blank") + demo_link = html.A( + "LEARN MORE", + href="https://plotly.com/dash/", + target="_blank", + className="demo-button", + ) + right_logos = html.Div([demo_link, link], className="header-logos") + + return html.Div([left_headers, right_logos], className="header") + + +def wind_speed_card(graph_id): + return html.Div([ + html.Div(html.H6("WIND SPEED (MPH)", className="graph__title")), + dcc.Graph( + id=graph_id, + figure=dict( + layout=dict( + plot_bgcolor=app_color["graph_bg"], + paper_bgcolor=app_color["graph_bg"], + ) + ), + ), + ], className="two-thirds column wind__speed__container", + ) + +def histogram_card(graph_id): + return html.Div([ + html.Div(html.H6("WIND SPEED HISTOGRAM", className="graph__title")), + html.Div( + dcc.Slider( + id="bin-slider", + min=1, + max=60, + step=1, + value=20, + marks={ + 20: {"label": "20"}, + 40: {"label": "40"}, + 60: {"label": "60"}, + }, + ), + className="slider", + ), + html.Div([ + dcc.Checklist( + id="bin-auto", + options=["Auto"], + value=["Auto"], + inputClassName="auto__checkbox", + labelClassName="auto__label", + ), + html.P( + "# of Bins: Auto", + id="bin-size", + className="auto__p", + )], + className="auto__container", + ), + dcc.Graph( + id=graph_id, + figure=dict( + layout=dict( + plot_bgcolor=app_color["graph_bg"], + paper_bgcolor=app_color["graph_bg"], + ) + ), + ), + ], className="graph__container first", + ) + +def wind_direction_card(graph_id): + return html.Div([ + html.Div(html.H6("WIND DIRECTION", className="graph__title")), + dcc.Graph( + id=graph_id, + figure=dict( + layout=dict( + plot_bgcolor=app_color["graph_bg"], + paper_bgcolor=app_color["graph_bg"], + ) + ), + ), + ], className="graph__container second", + ) \ No newline at end of file diff --git a/apps/dash-wind-streaming/db/api.py b/apps/dash-wind-streaming/utils/database_connection.py similarity index 87% rename from apps/dash-wind-streaming/db/api.py rename to apps/dash-wind-streaming/utils/database_connection.py index 5a21df66a..fea16418f 100644 --- a/apps/dash-wind-streaming/db/api.py +++ b/apps/dash-wind-streaming/utils/database_connection.py @@ -1,10 +1,7 @@ -import pathlib import sqlite3 import pandas as pd - -DB_FILE = pathlib.Path(__file__).resolve().parent.joinpath("wind-data.db").resolve() - +from constants import DB_FILE def get_wind_data(start, end): """ diff --git a/apps/dash-wind-streaming/utils/figures.py b/apps/dash-wind-streaming/utils/figures.py new file mode 100644 index 000000000..b209e47e5 --- /dev/null +++ b/apps/dash-wind-streaming/utils/figures.py @@ -0,0 +1,243 @@ + +import numpy as np +from scipy.stats import rayleigh +from dash.exceptions import PreventUpdate + +from constants import app_color +from utils.helper_functions import get_current_time +from utils.database_connection import get_wind_data, get_wind_data_by_id + + +def gen_wind_speed(): + """ + Generate the wind speed graph. + + :params interval: update the graph based on an interval + """ + + total_time = get_current_time() + df = get_wind_data(total_time - 200, total_time) + + trace = dict( + type="scatter", + y=df["Speed"], + line={"color": "#42C4F7"}, + hoverinfo="skip", + error_y={ + "type": "data", + "array": df["SpeedError"], + "thickness": 1.5, + "width": 2, + "color": "#B4E8FC", + }, + mode="lines", + ) + + layout = dict( + plot_bgcolor=app_color["graph_bg"], + paper_bgcolor=app_color["graph_bg"], + font={"color": "#fff"}, + height=700, + xaxis={ + "range": [0, 200], + "showline": True, + "zeroline": False, + "fixedrange": True, + "tickvals": [0, 50, 100, 150, 200], + "ticktext": ["200", "150", "100", "50", "0"], + "title": "Time Elapsed (sec)", + }, + yaxis={ + "range": [ + min(0, min(df["Speed"])), + max(45, max(df["Speed"]) + max(df["SpeedError"])), + ], + "showgrid": True, + "showline": True, + "fixedrange": True, + "zeroline": False, + "gridcolor": app_color["graph_line"], + "nticks": max(6, round(df["Speed"].iloc[-1] / 10)), + }, + ) + + return dict(data=[trace], layout=layout) + + + + +def gen_wind_direction(): + """ + Generate the wind direction graph. + + :params interval: update the graph based on an interval + """ + + total_time = get_current_time() + df = get_wind_data_by_id(total_time) + val = df["Speed"].iloc[-1] + direction = [0, (df["Direction"][0] - 20), (df["Direction"][0] + 20), 0] + + traces_scatterpolar = [ + {"r": [0, val, val, 0], "fillcolor": "#084E8A"}, + {"r": [0, val * 0.65, val * 0.65, 0], "fillcolor": "#B4E1FA"}, + {"r": [0, val * 0.3, val * 0.3, 0], "fillcolor": "#EBF5FA"}, + ] + + data = [ + dict( + type="scatterpolar", + r=traces["r"], + theta=direction, + mode="lines", + fill="toself", + fillcolor=traces["fillcolor"], + line={"color": "rgba(32, 32, 32, .6)", "width": 1}, + ) + for traces in traces_scatterpolar + ] + + layout = dict( + height=350, + plot_bgcolor=app_color["graph_bg"], + paper_bgcolor=app_color["graph_bg"], + font={"color": "#fff"}, + autosize=False, + polar={ + "bgcolor": app_color["graph_line"], + "radialaxis": {"range": [0, 45], "angle": 45, "dtick": 10}, + "angularaxis": {"showline": False, "tickcolor": "white"}, + }, + showlegend=False, + ) + + return dict(data=data, layout=layout) + + + +def gen_wind_histogram(wind_speed_figure, slider_value, auto_state): + """ + Genererate wind histogram graph. + + :params interval: upadte the graph based on an interval + :params wind_speed_figure: current wind speed graph + :params slider_value: current slider value + :params auto_state: current auto state + """ + + wind_val = [] + + try: + # Check to see whether wind-speed has been plotted yet + if wind_speed_figure is not None: + wind_val = wind_speed_figure["data"][0]["y"] + if "Auto" in auto_state: + bin_val = np.histogram( + wind_val, + bins=range(int(round(min(wind_val))), int(round(max(wind_val)))), + ) + else: + bin_val = np.histogram(wind_val, bins=slider_value) + except Exception as error: + raise PreventUpdate + + avg_val = float(sum(wind_val)) / len(wind_val) + median_val = np.median(wind_val) + + pdf_fitted = rayleigh.pdf( + bin_val[1], loc=(avg_val) * 0.55, scale=(bin_val[1][-1] - bin_val[1][0]) / 3 + ) + + y_val = (pdf_fitted * max(bin_val[0]) * 20,) + y_val_max = max(y_val[0]) + bin_val_max = max(bin_val[0]) + + trace = dict( + type="bar", + x=bin_val[1], + y=bin_val[0], + marker={"color": app_color["graph_line"]}, + showlegend=False, + hoverinfo="x+y", + ) + + traces_scatter = [ + {"line_dash": "dash", "line_color": "#2E5266", "name": "Average"}, + {"line_dash": "dot", "line_color": "#BD9391", "name": "Median"}, + ] + + scatter_data = [ + dict( + type="scatter", + x=[bin_val[int(len(bin_val) / 2)]], + y=[0], + mode="lines", + line={"dash": traces["line_dash"], "color": traces["line_color"]}, + marker={"opacity": 0}, + visible=True, + name=traces["name"], + ) + for traces in traces_scatter + ] + + trace3 = dict( + type="scatter", + mode="lines", + line={"color": "#42C4F7"}, + y=y_val[0], + x=bin_val[1][: len(bin_val[1])], + name="Rayleigh Fit", + ) + layout = dict( + height=350, + plot_bgcolor=app_color["graph_bg"], + paper_bgcolor=app_color["graph_bg"], + font={"color": "#fff"}, + xaxis={ + "title": "Wind Speed (mph)", + "showgrid": False, + "showline": False, + "fixedrange": True, + }, + yaxis={ + "showgrid": False, + "showline": False, + "zeroline": False, + "title": "Number of Samples", + "fixedrange": True, + }, + autosize=True, + bargap=0.01, + bargroupgap=0, + hovermode="closest", + legend={ + "orientation": "h", + "yanchor": "bottom", + "xanchor": "center", + "y": 1, + "x": 0.5, + }, + shapes=[ + { + "xref": "x", + "yref": "y", + "y1": int(max(bin_val_max, y_val_max)) + 0.5, + "y0": 0, + "x0": avg_val, + "x1": avg_val, + "type": "line", + "line": {"dash": "dash", "color": "#2E5266", "width": 5}, + }, + { + "xref": "x", + "yref": "y", + "y1": int(max(bin_val_max, y_val_max)) + 0.5, + "y0": 0, + "x0": median_val, + "x1": median_val, + "type": "line", + "line": {"dash": "dot", "color": "#BD9391", "width": 5}, + }, + ], + ) + return dict(data=[trace, scatter_data[0], scatter_data[1], trace3], layout=layout) diff --git a/apps/dash-wind-streaming/utils/helper_functions.py b/apps/dash-wind-streaming/utils/helper_functions.py new file mode 100644 index 000000000..b7efd5039 --- /dev/null +++ b/apps/dash-wind-streaming/utils/helper_functions.py @@ -0,0 +1,8 @@ +import datetime as dt + +def get_current_time(): + """Helper function to get the current time in seconds.""" + + now = dt.datetime.now() + total_time = (now.hour * 3600) + (now.minute * 60) + (now.second) + return total_time \ No newline at end of file