diff --git a/apps/dash-uber-rides-demo/.gitignore b/apps/dash-uber-rides-demo/.gitignore index 048af9b18..8e1af00b7 100644 --- a/apps/dash-uber-rides-demo/.gitignore +++ b/apps/dash-uber-rides-demo/.gitignore @@ -1,6 +1,190 @@ -venv -.vscode -*.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/ + +# Omit SQLite databases that may be produced by dash-snapshots in development +*.db + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + + +# Jupyter Notebook + +.ipynb_checkpoints +*/.ipynb_checkpoints/* + +# IPython +profile_default/ +ipython_config.py + +# Environments .env -output.csv \ 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-uber-rides-demo/README.md b/apps/dash-uber-rides-demo/README.md index b5d43456e..24209287b 100644 --- a/apps/dash-uber-rides-demo/README.md +++ b/apps/dash-uber-rides-demo/README.md @@ -47,7 +47,7 @@ You can run the app on your browser at http://127.0.0.1:8050 ## Screenshots -![demo.png](demo.png) +![demo.png](assets/github/demo.png) ## Resources diff --git a/apps/dash-uber-rides-demo/app.py b/apps/dash-uber-rides-demo/app.py index 00a8ec05e..2082567bf 100644 --- a/apps/dash-uber-rides-demo/app.py +++ b/apps/dash-uber-rides-demo/app.py @@ -1,228 +1,70 @@ -import dash -import dash_core_components as dcc -import dash_html_components as html -import pandas as pd -import numpy as np +from dash import Dash, dcc, html, Input, Output, callback +import dash_bootstrap_components as dbc -from dash.dependencies import Input, Output -from plotly import graph_objs as go -from plotly.graph_objs import * +import utils.figures as figs +from constants import totalList +from utils.helper_functions import total_rides_calculation +from utils.components import controls from datetime import datetime as dt - -app = dash.Dash( - __name__, meta_tags=[{"name": "viewport", "content": "width=device-width"}], -) -app.title = "New York Uber Rides" +app = Dash(__name__, title = "New York Uber Rides", external_stylesheets=[dbc.themes.BOOTSTRAP]) server = app.server -# Plotly mapbox public token -mapbox_access_token = "pk.eyJ1IjoicGxvdGx5bWFwYm94IiwiYSI6ImNrOWJqb2F4djBnMjEzbG50amg0dnJieG4ifQ.Zme1-Uzoi75IaFbieBDl3A" - -# Dictionary of important locations in New York -list_of_locations = { - "Madison Square Garden": {"lat": 40.7505, "lon": -73.9934}, - "Yankee Stadium": {"lat": 40.8296, "lon": -73.9262}, - "Empire State Building": {"lat": 40.7484, "lon": -73.9857}, - "New York Stock Exchange": {"lat": 40.7069, "lon": -74.0113}, - "JFK Airport": {"lat": 40.644987, "lon": -73.785607}, - "Grand Central Station": {"lat": 40.7527, "lon": -73.9772}, - "Times Square": {"lat": 40.7589, "lon": -73.9851}, - "Columbia University": {"lat": 40.8075, "lon": -73.9626}, - "United Nations HQ": {"lat": 40.7489, "lon": -73.9680}, -} - -# Initialize data frame -df1 = pd.read_csv( - "https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data1.csv", - dtype=object, -) -df2 = pd.read_csv( - "https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data2.csv", - dtype=object, -) -df3 = pd.read_csv( - "https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data3.csv", - dtype=object, -) -df = pd.concat([df1, df2, df3], axis=0) -df["Date/Time"] = pd.to_datetime(df["Date/Time"], format="%Y-%m-%d %H:%M") -df.index = df["Date/Time"] -df.drop("Date/Time", 1, inplace=True) -totalList = [] -for month in df.groupby(df.index.month): - dailyList = [] - for day in month[1].groupby(month[1].index.day): - dailyList.append(day[1]) - totalList.append(dailyList) -totalList = np.array(totalList) - # Layout of Dash App -app.layout = html.Div( - children=[ - html.Div( - className="row", - children=[ - # Column for user controls - html.Div( - className="four columns div-user-controls", - children=[ - html.A( - html.Img( - className="logo", - src=app.get_asset_url("dash-logo-new.png"), - ), - href="https://plotly.com/dash/", - ), - html.H2("DASH - UBER DATA APP"), - html.P( - """Select different days using the date picker or by selecting - different time frames on the histogram.""" - ), - html.Div( - className="div-for-dropdown", - children=[ - dcc.DatePickerSingle( - id="date-picker", - min_date_allowed=dt(2014, 4, 1), - max_date_allowed=dt(2014, 9, 30), - initial_visible_month=dt(2014, 4, 1), - date=dt(2014, 4, 1).date(), - display_format="MMMM D, YYYY", - style={"border": "0px solid black"}, - ) - ], - ), - # Change to side-by-side for mobile layout - html.Div( - className="row", - children=[ - html.Div( - className="div-for-dropdown", - children=[ - # Dropdown for locations on map - dcc.Dropdown( - id="location-dropdown", - options=[ - {"label": i, "value": i} - for i in list_of_locations - ], - placeholder="Select a location", - ) - ], - ), - html.Div( - className="div-for-dropdown", - children=[ - # Dropdown to select times - dcc.Dropdown( - id="bar-selector", - options=[ - { - "label": str(n) + ":00", - "value": str(n), - } - for n in range(24) - ], - multi=True, - placeholder="Select certain hours", - ) - ], - ), - ], - ), - html.P(id="total-rides"), - html.P(id="total-rides-selection"), - html.P(id="date-value"), - dcc.Markdown( - """ - Source: [FiveThirtyEight](https://github.com/fivethirtyeight/uber-tlc-foil-response/tree/master/uber-trip-data) - - Links: [Source Code](https://github.com/plotly/dash-sample-apps/tree/main/apps/dash-uber-rides-demo) | [Enterprise Demo](https://plotly.com/get-demo/) - """ - ), - ], - ), - # Column for app graphs and plots - html.Div( - className="eight columns div-for-charts bg-grey", - children=[ - dcc.Graph(id="map-graph"), - html.Div( - className="text-padding", - children=[ - "Select any of the bars on the histogram to section data by time." - ], - ), - dcc.Graph(id="histogram"), - ], - ), - ], - ) - ] -) - -# Gets the amount of days in the specified month -# Index represents month (0 is April, 1 is May, ... etc.) -daysInMonth = [30, 31, 30, 31, 31, 30] +# app.layout = html.Div( +# children=[ +# html.Div( +# className="row", +# children=[ +# # Column for user controls +# controls(app), +# # Column for app graphs and plots +# html.Div( +# className="eight columns div-for-charts bg-grey", +# children=[ +# dcc.Graph(id="map-graph", config={ 'displayModeBar': False }), +# html.Div( +# className="text-padding", +# children="Select any of the bars on the histogram to section data by time." +# ), +# dcc.Graph(id="histogram", config={ 'displayModeBar': False }), +# ], +# ), +# ], +# ) +# ] +# ) -# Get index for the specified month in the dataframe -monthIndex = pd.Index(["Apr", "May", "June", "July", "Aug", "Sept"]) -# Get the amount of rides per hour based on the time selected -# This also higlights the color of the histogram bars based on -# if the hours are selected -def get_selection(month, day, selection): - xVal = [] - yVal = [] - xSelected = [] - colorVal = [ - "#F4EC15", - "#DAF017", - "#BBEC19", - "#9DE81B", - "#80E41D", - "#66E01F", - "#4CDC20", - "#34D822", - "#24D249", - "#25D042", - "#26CC58", - "#28C86D", - "#29C481", - "#2AC093", - "#2BBCA4", - "#2BB5B8", - "#2C99B4", - "#2D7EB0", - "#2D65AC", - "#2E4EA4", - "#2E38A4", - "#3B2FA0", - "#4E2F9C", - "#603099", - ] - - # Put selected times into a list of numbers xSelected - xSelected.extend([int(x) for x in selection]) - - for i in range(24): - # If bar is selected then color it white - if i in xSelected and len(xSelected) < 24: - colorVal[i] = "#FFFFFF" - xVal.append(i) - # Get the number of rides at a particular time - yVal.append(len(totalList[month][day][totalList[month][day].index.hour == i])) - return [np.array(xVal), np.array(yVal), np.array(colorVal)] +# Layout of Dash App +app.layout = dbc.Row([ + # Column for user controls + controls(app), + + # Column for app graphs and plots + dbc.Col( + children=[ + dcc.Graph(id="map-graph", config={ 'displayModeBar': False }), + html.Div( + className="text-padding", + children="Select any of the bars on the histogram to section data by time." + ), + dcc.Graph(id="histogram", config={ 'displayModeBar': False }), + ], + className="div-for-charts bg-grey", + md=8, + ), +]) -# Selected Data in the Histogram updates the Values in the Hours selection dropdown menu -@app.callback( +@callback( Output("bar-selector", "value"), - [Input("histogram", "selectedData"), Input("histogram", "clickData")], + Input("histogram", "selectedData"), + Input("histogram", "clickData"), ) def update_bar_selector(value, clickData): + " Selected Data in the Histogram updates the Values in the Hours selection dropdown menu " holder = [] if clickData: holder.append(str(int(clickData["points"][0]["x"]))) @@ -232,276 +74,57 @@ def update_bar_selector(value, clickData): return list(set(holder)) -# Clear Selected Data if Click Data is used -@app.callback(Output("histogram", "selectedData"), [Input("histogram", "clickData")]) +@callback( + Output("histogram", "selectedData"), + Input("histogram", "clickData"), +) def update_selected_data(clickData): + " Clear Selected Data if Click Data is used " if clickData: return {"points": []} -# Update the total number of rides Tag -@app.callback(Output("total-rides", "children"), [Input("date-picker", "date")]) +@callback( + Output("total-rides", "children"), + Input("date-picker", "date"), +) def update_total_rides(datePicked): + " Update the total number of rides Tag " date_picked = dt.strptime(datePicked, "%Y-%m-%d") - return "Total Number of rides: {:,d}".format( - len(totalList[date_picked.month - 4][date_picked.day - 1]) - ) + total_rides = len(totalList[date_picked.month - 4][date_picked.day - 1]) + return f"Total Number of rides: {total_rides:,d}" -# Update the total number of rides in selected times -@app.callback( - [Output("total-rides-selection", "children"), Output("date-value", "children")], - [Input("date-picker", "date"), Input("bar-selector", "value")], +@callback( + Output("total-rides-selection", "children"), + Output("date-value", "children"), + Input("date-picker", "date"), + Input("bar-selector", "value"), ) -def update_total_rides_selection(datePicked, selection): - firstOutput = "" - - if selection is not None or len(selection) is not 0: - date_picked = dt.strptime(datePicked, "%Y-%m-%d") - totalInSelection = 0 - for x in selection: - totalInSelection += len( - totalList[date_picked.month - 4][date_picked.day - 1][ - totalList[date_picked.month - 4][date_picked.day - 1].index.hour - == int(x) - ] - ) - firstOutput = "Total rides in selection: {:,d}".format(totalInSelection) - - if ( - datePicked is None - or selection is None - or len(selection) is 24 - or len(selection) is 0 - ): - return firstOutput, (datePicked, " - showing hour(s): All") - - holder = sorted([int(x) for x in selection]) - - if holder == list(range(min(holder), max(holder) + 1)): - return ( - firstOutput, - ( - datePicked, - " - showing hour(s): ", - holder[0], - "-", - holder[len(holder) - 1], - ), - ) - - holder_to_string = ", ".join(str(x) for x in holder) - return firstOutput, (datePicked, " - showing hour(s): ", holder_to_string) +def update_total_rides_selection(date_picked, bars_selected): + " Update the total number of rides in selected times " + return total_rides_calculation(date_picked, bars_selected) -# Update Histogram Figure based on Month, Day and Times Chosen -@app.callback( +@callback( Output("histogram", "figure"), - [Input("date-picker", "date"), Input("bar-selector", "value")], + Input("date-picker", "date"), + Input("bar-selector", "value"), ) -def update_histogram(datePicked, selection): - date_picked = dt.strptime(datePicked, "%Y-%m-%d") - monthPicked = date_picked.month - 4 - dayPicked = date_picked.day - 1 - - [xVal, yVal, colorVal] = get_selection(monthPicked, dayPicked, selection) - - layout = go.Layout( - bargap=0.01, - bargroupgap=0, - barmode="group", - margin=go.layout.Margin(l=10, r=0, t=0, b=50), - showlegend=False, - plot_bgcolor="#323130", - paper_bgcolor="#323130", - dragmode="select", - font=dict(color="white"), - xaxis=dict( - range=[-0.5, 23.5], - showgrid=False, - nticks=25, - fixedrange=True, - ticksuffix=":00", - ), - yaxis=dict( - range=[0, max(yVal) + max(yVal) / 4], - showticklabels=False, - showgrid=False, - fixedrange=True, - rangemode="nonnegative", - zeroline=False, - ), - annotations=[ - dict( - x=xi, - y=yi, - text=str(yi), - xanchor="center", - yanchor="bottom", - showarrow=False, - font=dict(color="white"), - ) - for xi, yi in zip(xVal, yVal) - ], - ) - - return go.Figure( - data=[ - go.Bar(x=xVal, y=yVal, marker=dict(color=colorVal), hoverinfo="x"), - go.Scatter( - opacity=0, - x=xVal, - y=yVal / 2, - hoverinfo="none", - mode="markers", - marker=dict(color="rgb(66, 134, 244, 0)", symbol="square", size=40), - visible=True, - ), - ], - layout=layout, - ) - +def update_histogram(date_picked, bars_selected): + " Update Histogram Figure based on Month, Day and Times Chosen " + return figs.histogram(date_picked, bars_selected) -# Get the Coordinates of the chosen months, dates and times -def getLatLonColor(selectedData, month, day): - listCoords = totalList[month][day] - # No times selected, output all times for chosen month and date - if selectedData is None or len(selectedData) is 0: - return listCoords - listStr = "listCoords[" - for time in selectedData: - if selectedData.index(time) is not len(selectedData) - 1: - listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ") | " - else: - listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ")]" - return eval(listStr) - - -# Update Map Graph based on date-picker, selected data on histogram and location dropdown -@app.callback( +@callback( Output("map-graph", "figure"), - [ - Input("date-picker", "date"), - Input("bar-selector", "value"), - Input("location-dropdown", "value"), - ], + Input("date-picker", "date"), + Input("bar-selector", "value"), + Input("location-dropdown", "value"), ) -def update_graph(datePicked, selectedData, selectedLocation): - zoom = 12.0 - latInitial = 40.7272 - lonInitial = -73.991251 - bearing = 0 - - if selectedLocation: - zoom = 15.0 - latInitial = list_of_locations[selectedLocation]["lat"] - lonInitial = list_of_locations[selectedLocation]["lon"] - - date_picked = dt.strptime(datePicked, "%Y-%m-%d") - monthPicked = date_picked.month - 4 - dayPicked = date_picked.day - 1 - listCoords = getLatLonColor(selectedData, monthPicked, dayPicked) - - return go.Figure( - data=[ - # Data for all rides based on date and time - Scattermapbox( - lat=listCoords["Lat"], - lon=listCoords["Lon"], - mode="markers", - hoverinfo="lat+lon+text", - text=listCoords.index.hour, - marker=dict( - showscale=True, - color=np.append(np.insert(listCoords.index.hour, 0, 0), 23), - opacity=0.5, - size=5, - colorscale=[ - [0, "#F4EC15"], - [0.04167, "#DAF017"], - [0.0833, "#BBEC19"], - [0.125, "#9DE81B"], - [0.1667, "#80E41D"], - [0.2083, "#66E01F"], - [0.25, "#4CDC20"], - [0.292, "#34D822"], - [0.333, "#24D249"], - [0.375, "#25D042"], - [0.4167, "#26CC58"], - [0.4583, "#28C86D"], - [0.50, "#29C481"], - [0.54167, "#2AC093"], - [0.5833, "#2BBCA4"], - [1.0, "#613099"], - ], - colorbar=dict( - title="Time of
Day", - x=0.93, - xpad=0, - nticks=24, - tickfont=dict(color="#d8d8d8"), - titlefont=dict(color="#d8d8d8"), - thicknessmode="pixels", - ), - ), - ), - # Plot of important locations on the map - Scattermapbox( - lat=[list_of_locations[i]["lat"] for i in list_of_locations], - lon=[list_of_locations[i]["lon"] for i in list_of_locations], - mode="markers", - hoverinfo="text", - text=[i for i in list_of_locations], - marker=dict(size=8, color="#ffa0a0"), - ), - ], - layout=Layout( - autosize=True, - margin=go.layout.Margin(l=0, r=35, t=0, b=0), - showlegend=False, - mapbox=dict( - accesstoken=mapbox_access_token, - center=dict(lat=latInitial, lon=lonInitial), # 40.7272 # -73.991251 - style="dark", - bearing=bearing, - zoom=zoom, - ), - updatemenus=[ - dict( - buttons=( - [ - dict( - args=[ - { - "mapbox.zoom": 12, - "mapbox.center.lon": "-73.991251", - "mapbox.center.lat": "40.7272", - "mapbox.bearing": 0, - "mapbox.style": "dark", - } - ], - label="Reset Zoom", - method="relayout", - ) - ] - ), - direction="left", - pad={"r": 0, "t": 0, "b": 0, "l": 0}, - showactive=False, - type="buttons", - x=0.45, - y=0.02, - xanchor="left", - yanchor="bottom", - bgcolor="#323130", - borderwidth=1, - bordercolor="#6d6d6d", - font=dict(color="#FFFFFF"), - ) - ], - ), - ) +def update_graph(date_picked, bars_selected, location): + " Update Map Graph based on date-picker, selected data on histogram and location dropdown " + return figs.map(date_picked, bars_selected, location) if __name__ == "__main__": diff --git a/apps/dash-uber-rides-demo/assets/base.css b/apps/dash-uber-rides-demo/assets/base.css deleted file mode 100644 index 98d7d1891..000000000 --- a/apps/dash-uber-rides-demo/assets/base.css +++ /dev/null @@ -1,392 +0,0 @@ -/* Table of contents –––––––––––––––––––––––––––––––––––––––––––––––––– -Taken from https://codepen.io/chriddyp/pen/bWLwgP.css -- Grid -- Base Styles - - Typography - - Links - - Buttons - - Forms - - Lists - - Code - - Tables - - Spacing - - Utilities - - Clearing - - Media Queries - - Custom App CSS */ - - -/* Grid –––––––––––––––––––––––––––––––––––––––––––––––––– */ -.container { - position: relative; - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 20px; - box-sizing: border-box; - } - .column, .columns { - width: 100%; - float: left; - box-sizing: border-box; - } - /* For devices larger than 400px */ - @media (min-width: 400px) { - .container { - width: 85%; - padding: 0; - } - } - /* For devices larger than 550px */ - @media (min-width: 550px) { - .container { - width: 80%; - } - .column, .columns { - margin-left: 4%; - } - .column:first-child, .columns:first-child { - margin-left: 0; - } - .one.column, .one.columns { - width: 4.66666666667%; - } - .two.columns { - width: 13.3333333333%; - } - .three.columns { - width: 22%; - } - .four.columns { - width: 30.6666666667%; - } - .five.columns { - width: 39.3333333333%; - } - .six.columns { - width: 48%; - } - .seven.columns { - width: 56.6666666667%; - } - .eight.columns { - width: 65.3333333333%; - } - .nine.columns { - width: 74.0%; - } - .ten.columns { - width: 82.6666666667%; - } - .eleven.columns { - width: 91.3333333333%; - } - .twelve.columns { - width: 100%; - margin-left: 0; - } - .one-third.column { - width: 30.6666666667%; - } - .two-thirds.column { - width: 65.3333333333%; - } - .one-half.column { - width: 48%; - } - /* Offsets */ - .offset-by-one.column, .offset-by-one.columns { - margin-left: 8.66666666667%; - } - .offset-by-two.column, .offset-by-two.columns { - margin-left: 17.3333333333%; - } - .offset-by-three.column, .offset-by-three.columns { - margin-left: 26%; - } - .offset-by-four.column, .offset-by-four.columns { - margin-left: 34.6666666667%; - } - .offset-by-five.column, .offset-by-five.columns { - margin-left: 43.3333333333%; - } - .offset-by-six.column, .offset-by-six.columns { - margin-left: 52%; - } - .offset-by-seven.column, .offset-by-seven.columns { - margin-left: 60.6666666667%; - } - .offset-by-eight.column, .offset-by-eight.columns { - margin-left: 69.3333333333%; - } - .offset-by-nine.column, .offset-by-nine.columns { - margin-left: 78.0%; - } - .offset-by-ten.column, .offset-by-ten.columns { - margin-left: 86.6666666667%; - } - .offset-by-eleven.column, .offset-by-eleven.columns { - margin-left: 95.3333333333%; - } - .offset-by-one-third.column, .offset-by-one-third.columns { - margin-left: 34.6666666667%; - } - .offset-by-two-thirds.column, .offset-by-two-thirds.columns { - margin-left: 69.3333333333%; - } - .offset-by-one-half.column, .offset-by-one-half.columns { - margin-left: 52%; - } - } - /* Base Styles –––––––––––––––––––––––––––––––––––––––––––––––––– */ - /* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */ - html { - font-size: 62.5%; - } - body { - font-size: 1.5em; - /* currently ems cause chrome bug misinterpreting rems on body element */ - line-height: 1.6; - font-weight: 400; - font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: rgb(50, 50, 50); - } - /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ - h1, h2, h3, h4, h5, h6 { - margin-top: 0; - margin-bottom: 0; - font-weight: 300; - } - h1 { - font-size: 4.5rem; - line-height: 1.2; - letter-spacing: -.1rem; - margin-bottom: 2rem; - } - h2 { - font-size: 3.6rem; - line-height: 1.25; - letter-spacing: -.1rem; - margin-bottom: 1.8rem; - margin-top: 1.8rem; - } - h3 { - font-size: 3.0rem; - line-height: 1.3; - letter-spacing: -.1rem; - margin-bottom: 1.5rem; - margin-top: 1.5rem; - } - h4 { - font-size: 2.6rem; - line-height: 1.35; - letter-spacing: -.08rem; - margin-bottom: 1.2rem; - margin-top: 1.2rem; - } - h5 { - font-size: 2.2rem; - line-height: 1.5; - letter-spacing: -.05rem; - margin-bottom: 0.6rem; - margin-top: 0.6rem; - } - h6 { - font-size: 2.0rem; - line-height: 1.6; - letter-spacing: 0; - margin-bottom: 0.75rem; - margin-top: 0.75rem; - } - p { - margin-top: 0; - } - /* Blockquotes –––––––––––––––––––––––––––––––––––––––––––––––––– */ - blockquote { - border-left: 4px lightgrey solid; - padding-left: 1rem; - margin-top: 2rem; - margin-bottom: 2rem; - margin-left: 0rem; - } - /* Links –––––––––––––––––––––––––––––––––––––––––––––––––– */ - a { - color: #1EAEDB; - text-decoration: underline; - cursor: pointer; - } - a:hover { - color: #0FA0CE; - } - /* Buttons –––––––––––––––––––––––––––––––––––––––––––––––––– */ - .button, button, input[type="submit"], input[type="reset"], input[type="button"] { - display: inline-block; - height: 38px; - padding: 0 30px; - color: #555; - text-align: center; - font-size: 11px; - font-weight: 600; - line-height: 38px; - letter-spacing: .1rem; - text-transform: uppercase; - text-decoration: none; - white-space: nowrap; - background-color: transparent; - border-radius: 4px; - border: 1px solid #bbb; - cursor: pointer; - box-sizing: border-box; - } - .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus { - color: #333; - border-color: #888; - outline: 0; - } - .button.button-primary, button.button-primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary { - color: #FFF; - background-color: #33C3F0; - border-color: #33C3F0; - } - .button.button-primary:hover, button.button-primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus { - color: #FFF; - background-color: #1EAEDB; - border-color: #1EAEDB; - } - /* Forms –––––––––––––––––––––––––––––––––––––––––––––––––– */ - input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select { - height: 38px; - padding: 6px 10px; - /* The 6px vertically centers text on FF, ignored by Webkit */ - background-color: #fff; - border: 1px solid #D1D1D1; - border-radius: 4px; - box-shadow: none; - box-sizing: border-box; - font-family: inherit; - font-size: inherit; - /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/ - } - /* Removes awkward default styles on some inputs for iOS */ - input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - } - textarea { - min-height: 65px; - padding-top: 6px; - padding-bottom: 6px; - } - input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus { - border: 1px solid #33C3F0; - outline: 0; - } - label, legend { - display: block; - margin-bottom: 0px; - } - fieldset { - padding: 0; - border-width: 0; - } - input[type="checkbox"], input[type="radio"] { - display: inline; - } - label > .label-body { - display: inline-block; - margin-left: .5rem; - font-weight: normal; - } - /* Lists –––––––––––––––––––––––––––––––––––––––––––––––––– */ - ul { - list-style: circle inside; - } - ol { - list-style: decimal inside; - } - ol, ul { - padding-left: 0; - margin-top: 0; - } - ul ul, ul ol, ol ol, ol ul { - margin: 1.5rem 0 1.5rem 3rem; - font-size: 90%; - } - li { - margin-bottom: 1rem; - } - /* Tables –––––––––––––––––––––––––––––––––––––––––––––––––– */ - table { - border-collapse: collapse; - } - th, td { - padding: 12px 15px; - text-align: left; - border-bottom: 1px solid #E1E1E1; - } - th:first-child, td:first-child { - padding-left: 0; - } - th:last-child, td:last-child { - padding-right: 0; - } - /* Spacing –––––––––––––––––––––––––––––––––––––––––––––––––– */ - button, .button { - margin-bottom: 0rem; - } - input, textarea, select, fieldset { - margin-bottom: 0rem; - } - pre, dl, figure, table, form { - margin-bottom: 0rem; - } - p, ul, ol { - margin-bottom: 0.75rem; - } - /* Utilities –––––––––––––––––––––––––––––––––––––––––––––––––– */ - .u-full-width { - width: 100%; - box-sizing: border-box; - } - .u-max-full-width { - max-width: 100%; - box-sizing: border-box; - } - .u-pull-right { - float: right; - } - .u-pull-left { - float: left; - } - /* Misc –––––––––––––––––––––––––––––––––––––––––––––––––– */ - hr { - margin-top: 3rem; - margin-bottom: 3.5rem; - border-width: 0; - border-top: 1px solid #E1E1E1; - } - /* Clearing –––––––––––––––––––––––––––––––––––––––––––––––––– */ - /* Self Clearing Goodness */ - .container:after, .row:after, .u-cf { - content: ""; - display: table; - clear: both; - } - /* Media Queries –––––––––––––––––––––––––––––––––––––––––––––––––– */ - /* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */ - /* Larger than mobile */ - @media (min-width: 400px) { - } - /* Larger than phablet (also point when grid becomes active) */ - @media (min-width: 550px) { - } - /* Larger than tablet */ - @media (min-width: 750px) { - } - /* Larger than desktop */ - @media (min-width: 1000px) { - } - /* Larger than Desktop HD */ - @media (min-width: 1200px) { - } \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/assets/style.css b/apps/dash-uber-rides-demo/assets/css/style.css similarity index 82% rename from apps/dash-uber-rides-demo/assets/style.css rename to apps/dash-uber-rides-demo/assets/css/style.css index 9df9a2de6..46c48ad97 100644 --- a/apps/dash-uber-rides-demo/assets/style.css +++ b/apps/dash-uber-rides-demo/assets/css/style.css @@ -6,8 +6,6 @@ body { background-color: #1E1E1E; color: #d8d8d8; - margin: 0; - padding: 0; } h1, h2, h3, h4, h5 { font-family: "Open Sans"; @@ -22,9 +20,6 @@ p { font-size: 14px; padding-left: 12px; } -a { - text-decoration: none; -} .bg-grey{ background-color: #31302F; } @@ -36,7 +31,7 @@ a { display: flex; flex-direction: column; height: 100vh; - width: 100%; + /* width: 100%; */ } #histogram { flex-grow: 1 @@ -60,11 +55,17 @@ a { width: 97%; text-align: center; } -.logo { + +.logos { + display: flex; + justify-content: space-between; +} +.logos img { height: 35px; padding-bottom: 12px; margin-left: 8px; } + .Select-control, .Select-menu-outer, .Select-multi-value-wrapper, .select-up, .is-open .Select-control { background-color: #1E1E1E; color: white; @@ -157,7 +158,6 @@ a { } .div-for-charts { padding: 0px; - width: 100%; text-align: center; } @@ -207,3 +207,41 @@ a { .CalendarMonth_table td { padding: unset; } + + + +/* 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; + vertical-align: super; +} + +.demo-button:hover { + color: #7A76FF; + background-position: 0%; +} \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/assets/dash-logo-new.png b/apps/dash-uber-rides-demo/assets/dash-logo-new.png deleted file mode 100644 index 040cde174..000000000 Binary files a/apps/dash-uber-rides-demo/assets/dash-logo-new.png and /dev/null differ diff --git a/apps/dash-uber-rides-demo/demo.png b/apps/dash-uber-rides-demo/assets/github/demo.png similarity index 100% rename from apps/dash-uber-rides-demo/demo.png rename to apps/dash-uber-rides-demo/assets/github/demo.png diff --git a/apps/dash-uber-rides-demo/assets/images/plotly-logo-dark-theme.png b/apps/dash-uber-rides-demo/assets/images/plotly-logo-dark-theme.png new file mode 100644 index 000000000..984dd57ab Binary files /dev/null and b/apps/dash-uber-rides-demo/assets/images/plotly-logo-dark-theme.png differ diff --git a/apps/dash-uber-rides-demo/constants.py b/apps/dash-uber-rides-demo/constants.py new file mode 100644 index 000000000..d58ceef45 --- /dev/null +++ b/apps/dash-uber-rides-demo/constants.py @@ -0,0 +1,44 @@ +import pandas as pd +import numpy as np + +# Plotly mapbox public token +mapbox_access_token = "pk.eyJ1IjoicGxvdGx5bWFwYm94IiwiYSI6ImNrOWJqb2F4djBnMjEzbG50amg0dnJieG4ifQ.Zme1-Uzoi75IaFbieBDl3A" + +# Dictionary of important locations in New York +dict_of_locations = { + "Madison Square Garden": {"lat": 40.7505, "lon": -73.9934}, + "Yankee Stadium": {"lat": 40.8296, "lon": -73.9262}, + "Empire State Building": {"lat": 40.7484, "lon": -73.9857}, + "New York Stock Exchange": {"lat": 40.7069, "lon": -74.0113}, + "JFK Airport": {"lat": 40.644987, "lon": -73.785607}, + "Grand Central Station": {"lat": 40.7527, "lon": -73.9772}, + "Times Square": {"lat": 40.7589, "lon": -73.9851}, + "Columbia University": {"lat": 40.8075, "lon": -73.9626}, + "United Nations HQ": {"lat": 40.7489, "lon": -73.9680}, +} + +# Initialize data frame +df1 = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data1.csv", dtype=object ) +df2 = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data2.csv", dtype=object ) +df3 = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/uber-rides-data3.csv", dtype=object ) +df = pd.concat([df1, df2, df3], axis=0) + +df["Date/Time"] = pd.to_datetime(df["Date/Time"], format="%Y-%m-%d %H:%M") +df.index = df["Date/Time"] +df.drop("Date/Time", axis=1, inplace=True) +totalList = [] +for month in df.groupby(df.index.month): + dailyList = [] + for day in month[1].groupby(month[1].index.day): + dailyList.append(day[1]) + totalList.append(dailyList) +# totalList = np.array(totalList) + + + +# Gets the amount of days in the specified month +# Index represents month (0 is April, 1 is May, ... etc.) +daysInMonth = [30, 31, 30, 31, 31, 30] + +# Get index for the specified month in the dataframe +monthIndex = pd.Index(["Apr", "May", "June", "July", "Aug", "Sept"]) \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/requirements.txt b/apps/dash-uber-rides-demo/requirements.txt index f3f277d5f..ffe41dfb0 100644 --- a/apps/dash-uber-rides-demo/requirements.txt +++ b/apps/dash-uber-rides-demo/requirements.txt @@ -1,3 +1,4 @@ -dash==1.12.0 -pandas==0.24.1 -gunicorn==19.9.0 \ No newline at end of file +dash==2.4.1 +pandas==1.4.2 +gunicorn==20.1.0 +dash-bootstrap-components==1.2.1 \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/runtime.txt b/apps/dash-uber-rides-demo/runtime.txt new file mode 100644 index 000000000..cfa660c42 --- /dev/null +++ b/apps/dash-uber-rides-demo/runtime.txt @@ -0,0 +1 @@ +python-3.8.0 \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/utils/components.py b/apps/dash-uber-rides-demo/utils/components.py new file mode 100644 index 000000000..441ad22b7 --- /dev/null +++ b/apps/dash-uber-rides-demo/utils/components.py @@ -0,0 +1,75 @@ +from datetime import datetime as dt +from dash import html, dcc +import dash_bootstrap_components as dbc + +from constants import dict_of_locations + +def controls(app): + return dbc.Col( + className="div-user-controls", + md=4, + children=[ + html.Div([ + html.A( + html.Img( + className="logo", + src=app.get_asset_url("images/plotly-logo-dark-theme.png"), + ), + href="https://plotly.com/get-demo/", + ), + html.A("LEARN MORE", href="https://plotly.com/dash/", target="_blank", className="demo-button") + ], className="logos"), + + html.H2("DASH - UBER DATA APP"), + html.P( + """Select different days using the date picker or by selecting + different time frames on the histogram.""" + ), + + dcc.DatePickerSingle( + id="date-picker", + min_date_allowed=dt(2014, 4, 1), + max_date_allowed=dt(2014, 9, 30), + initial_visible_month=dt(2014, 4, 1), + date=dt(2014, 4, 1).date(), + display_format="MMMM D, YYYY", + style={"border": "0px solid black"}, + className="div-for-dropdown", + ), + + dbc.Row([ + # Dropdown for locations on map + dbc.Col( + dcc.Dropdown( + id="location-dropdown", + options=[{'label': k, 'value': k} for k, v in dict_of_locations.items()], + placeholder="Select a location", + className="div-for-dropdown", + ), + sm=6, + ), + + # Dropdown to select times + dbc.Col( + dcc.Dropdown( + id="bar-selector", + options=[ + { + "label": str(n) + ":00", + "value": str(n), + } + for n in range(24) + ], + multi=True, + placeholder="Select certain hours", + className="div-for-dropdown", + ), + sm=6, + ), + ]), + + html.P(id="total-rides"), + html.P(id="total-rides-selection"), + html.P(id="date-value"), + ], +) \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/utils/figures.py b/apps/dash-uber-rides-demo/utils/figures.py new file mode 100644 index 000000000..ada9f9cb7 --- /dev/null +++ b/apps/dash-uber-rides-demo/utils/figures.py @@ -0,0 +1,185 @@ +from datetime import datetime as dt +from plotly import graph_objs as go +import numpy as np + +from utils.helper_functions import get_selection, getLatLonColor +from constants import dict_of_locations, mapbox_access_token + +def map(date_picked, bars_selected, location): + zoom = 12.0 + latInitial = 40.7272 + lonInitial = -73.991251 + bearing = 0 + + if location: + zoom = 15.0 + latInitial = dict_of_locations[location]["lat"] + lonInitial = dict_of_locations[location]["lon"] + + date_picked = dt.strptime(date_picked, "%Y-%m-%d") + monthPicked = date_picked.month - 4 + dayPicked = date_picked.day - 1 + listCoords = getLatLonColor(bars_selected, monthPicked, dayPicked) + + return go.Figure( + data=[ + # Data for all rides based on date and time + go.Scattermapbox( + lat=listCoords["Lat"], + lon=listCoords["Lon"], + mode="markers", + hoverinfo="lat+lon+text", + text=listCoords.index.hour, + marker=dict( + showscale=True, + color=np.append(np.insert(listCoords.index.hour, 0, 0), 23), + opacity=0.5, + size=5, + colorscale=[ + [0, "#F4EC15"], + [0.04167, "#DAF017"], + [0.0833, "#BBEC19"], + [0.125, "#9DE81B"], + [0.1667, "#80E41D"], + [0.2083, "#66E01F"], + [0.25, "#4CDC20"], + [0.292, "#34D822"], + [0.333, "#24D249"], + [0.375, "#25D042"], + [0.4167, "#26CC58"], + [0.4583, "#28C86D"], + [0.50, "#29C481"], + [0.54167, "#2AC093"], + [0.5833, "#2BBCA4"], + [1.0, "#613099"], + ], + colorbar=dict( + title="Time of
Day", + x=0.93, + xpad=0, + nticks=24, + tickfont=dict(color="#d8d8d8"), + titlefont=dict(color="#d8d8d8"), + thicknessmode="pixels", + ), + ), + ), + # Plot of important locations on the map + go.Scattermapbox( + lat=[dict_of_locations[i]["lat"] for i in dict_of_locations], + lon=[dict_of_locations[i]["lon"] for i in dict_of_locations], + mode="markers", + hoverinfo="text", + text=[i for i in dict_of_locations], + marker=dict(size=8, color="#ffa0a0"), + ), + ], + layout=go.Layout( + autosize=True, + margin=go.layout.Margin(l=0, r=35, t=0, b=0), + showlegend=False, + mapbox=dict( + accesstoken=mapbox_access_token, + center=dict(lat=latInitial, lon=lonInitial), # 40.7272 # -73.991251 + style="dark", + bearing=bearing, + zoom=zoom, + ), + updatemenus=[ + dict( + buttons=( + [ + dict( + args=[ + { + "mapbox.zoom": 12, + "mapbox.center.lon": "-73.991251", + "mapbox.center.lat": "40.7272", + "mapbox.bearing": 0, + "mapbox.style": "dark", + } + ], + label="Reset Zoom", + method="relayout", + ) + ] + ), + direction="left", + pad={"r": 0, "t": 0, "b": 0, "l": 0}, + showactive=False, + type="buttons", + x=0.45, + y=0.02, + xanchor="left", + yanchor="bottom", + bgcolor="#323130", + borderwidth=1, + bordercolor="#6d6d6d", + font=dict(color="#FFFFFF"), + ) + ], + ), + ) + + +def histogram(date_picked, bars_selected): + date_picked = dt.strptime(date_picked, "%Y-%m-%d") + monthPicked = date_picked.month - 4 + dayPicked = date_picked.day - 1 + + [xVal, yVal, colorVal] = get_selection(monthPicked, dayPicked, bars_selected) + + layout = go.Layout( + bargap=0.01, + bargroupgap=0, + barmode="group", + margin=go.layout.Margin(l=10, r=0, t=0, b=50), + showlegend=False, + plot_bgcolor="#323130", + paper_bgcolor="#323130", + dragmode="select", + font=dict(color="white"), + xaxis=dict( + range=[-0.5, 23.5], + showgrid=False, + nticks=25, + fixedrange=True, + ticksuffix=":00", + ), + yaxis=dict( + range=[0, max(yVal) + max(yVal) / 4], + showticklabels=False, + showgrid=False, + fixedrange=True, + rangemode="nonnegative", + zeroline=False, + ), + annotations=[ + dict( + x=xi, + y=yi, + text=str(yi), + xanchor="center", + yanchor="bottom", + showarrow=False, + font=dict(color="white"), + ) + for xi, yi in zip(xVal, yVal) + ], + ) + + return go.Figure( + data=[ + go.Bar(x=xVal, y=yVal, marker=dict(color=colorVal), hoverinfo="x"), + go.Scatter( + opacity=0, + x=xVal, + y=yVal / 2, + hoverinfo="none", + mode="markers", + marker=dict(color="rgb(66, 134, 244, 0)", symbol="square", size=40), + visible=True, + ), + ], + layout=layout, + ) \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/utils/helper_functions.py b/apps/dash-uber-rides-demo/utils/helper_functions.py new file mode 100644 index 000000000..53aa3ca04 --- /dev/null +++ b/apps/dash-uber-rides-demo/utils/helper_functions.py @@ -0,0 +1,105 @@ +from datetime import datetime as dt +import numpy as np + +from constants import totalList + +def get_selection(month, day, selection): + """ + Get the amount of rides per hour based on the time selected + This also higlights the color of the histogram bars based on + if the hours are selected + """ + xVal, yVal, xSelected = [], [], [] + colorVal = [ + "#F4EC15", + "#DAF017", + "#BBEC19", + "#9DE81B", + "#80E41D", + "#66E01F", + "#4CDC20", + "#34D822", + "#24D249", + "#25D042", + "#26CC58", + "#28C86D", + "#29C481", + "#2AC093", + "#2BBCA4", + "#2BB5B8", + "#2C99B4", + "#2D7EB0", + "#2D65AC", + "#2E4EA4", + "#2E38A4", + "#3B2FA0", + "#4E2F9C", + "#603099", + ] + + # Put selected times into a list of numbers xSelected + xSelected.extend([int(x) for x in selection]) + + for i in range(24): + # If bar is selected then color it white + if i in xSelected and len(xSelected) < 24: + colorVal[i] = "#FFFFFF" + xVal.append(i) + # Get the number of rides at a particular time + yVal.append(len(totalList[month][day][totalList[month][day].index.hour == i])) + return [np.array(xVal), np.array(yVal), np.array(colorVal)] + + +def getLatLonColor(selectedData, month, day): + " Get the Coordinates of the chosen months, dates and times " + listCoords = totalList[month][day] + + # No times selected, output all times for chosen month and date + if selectedData is None or len(selectedData) == 0: + return listCoords + listStr = "listCoords[" + for time in selectedData: + if selectedData.index(time) != len(selectedData) - 1: + listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ") | " + else: + listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ")]" + return eval(listStr) + + +def total_rides_calculation(date_picked, bars_selected): + + firstOutput = "" + + if bars_selected is not None or len(bars_selected) != 0: + date_temp = dt.strptime(date_picked, "%Y-%m-%d") + totalInSelection = 0 + for x in bars_selected: + totalInSelection += len( + totalList[date_temp.month - 4][date_temp.day - 1][ + totalList[date_temp.month - 4][date_temp.day - 1].index.hour + == int(x) + ] + ) + firstOutput = f"Total rides in selection: {totalInSelection:,d}" + + if (bars_selected is None + or bars_selected is None + or len(bars_selected)==24 + or len(bars_selected)==0 + ): + return firstOutput, (date_picked, " - showing hour(s): All") + + holder = sorted([int(x) for x in bars_selected]) + if holder == list(range(min(holder), max(holder) + 1)): + return ( + firstOutput, + ( + date_picked, + " - showing hour(s): ", + holder[0], + "-", + holder[len(holder) - 1], + ), + ) + holder_to_string = ", ".join(str(x) for x in holder) + return firstOutput, (date_picked, " - showing hour(s): ", holder_to_string) \ No newline at end of file diff --git a/apps/dash-uber-rides-demo/utils/model.py b/apps/dash-uber-rides-demo/utils/model.py new file mode 100644 index 000000000..e69de29bb