Hey all,
long story short, I was put in charge of creating a user interface for my senior design project. I’ve had no formal training in python(or really coding at all, just matlab). It was quite the process. I’m sure my code is as inefficient as they come, but hey it works. There is only one problem: when under the ‘Graphs’ tab, each time dcc.Interval updated the figures, it resets to the default view which is no graph. I would love to have it stay on the selected graph, but I have an idea. Roasts, tips, tricks, or comments on my code are all appricated:
from dash import Dash, dcc, html, Input, Output, callback
import pandas
import sqlite3
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
import plotly.graph_objects as go
#create app layout
#multi-tab layout to use on 5" touchscreen
#avoid having to scroll to view content
app = Dash()
app.layout = html.Div([
html.Div(
html.H1('MU AIR: Display Your Local Air Quality')
),
dcc.Tabs(id="tabs", value='tab-ataglance', children=[
dcc.Tab(label='At A Glance', id='tabs-glance', value='tab-ataglance', children=[
]),
dcc.Tab(label='Graphs', id='tabs-graph', value='tabs-anygraph', children=[
]),
]),
#interval to update all figures
dcc.Interval(id='interval_component',
interval=5*1000
),
html.Div(id='tabs-show'
),
])
#Callbacks
#'At a Glance' tab
@app.callback(Output('tabs-glance', 'children'),
Input('tabs', 'value'),
Input('interval_component','n_intervals'),
)
def update_tabs(value, n_intervals):
#fetch single latest value for instant temp/humidity/aqi display
conn=sqlite3.connect('MUAIRtestdatabase', check_same_thread=False)
cursor = conn.cursor()
cursor.execute("SELECT avg FROM temp ORDER BY rowid DESC LIMIT 1")
tempvalue, = cursor.fetchone()
cursor.execute("SELECT avg FROM humidity ORDER BY rowid DESC LIMIT 1")
humidityvalue, = cursor.fetchone()
#section for all the figures
#humidity and temp
fighumidity = go.Figure()
fighumidity.add_trace(go.Indicator(
mode = "number+gauge",
value = tempvalue,
domain = {'x': [0.25, 1], 'y': [0.4, 0.6]},
title = {'text': "Temperature", 'font':{'size': 25}},
number={'suffix': "°F", 'font': {'size': 45}},
gauge = {
'shape': "bullet",
'axis': {'range': [-30, 120]},
'threshold': {
'line': {'color': "lightblue", 'width': 6},
'thickness': 1,
'value': -20},
'bar':
{'color': "darkred", 'thickness': .8}
}))
fighumidity.add_trace(go.Indicator(
mode = "number+gauge",
value = humidityvalue,
domain = {'x': [0.25, 1], 'y': [0.0, 0.2]},
title = {'text': "Humidity", 'font':{'size': 25}},
number={'suffix': "%", 'font': {'size': 45}},
gauge = {
'shape': "bullet",
'axis': {'range': [0, 100]},
'bar': {'color': "blue", 'thickness': 0.8}}))
#AQI
cursor.execute("SELECT adjustedaqi FROM aqi ORDER BY rowid DESC LIMIT 1")
aqidata, = cursor.fetchone()
if 0 <= aqidata <= 50:
barcolor = 'lightgreen'
elif 51 <= aqidata <= 100:
barcolor = 'yellow'
elif 101 <= aqidata <= 150:
barcolor = 'orange'
elif 151 <= aqidata <= 200:
barcolor = 'red'
elif 201 <= aqidata <= 300:
barcolor = 'purple'
elif 301 <= aqidata:
barcolor = 'darkred'
else :
pass
figaqi = go.Figure(go.Indicator(
mode = "gauge+number",
value = aqidata,
domain = {'x': [0, 1], 'y': [0, 1]},
title = {'text': "AQI", 'font': {'size': 45}},
gauge = {
'axis': {'range': [None, 400], 'tickwidth': 1, 'tickcolor': "black"},
'bar': {'color': barcolor},
'bgcolor': "white",
'borderwidth': 2,
'bordercolor': "gray",
'steps': [
{'range': [0, 400], 'color': 'lightblue'}],
'threshold': {
'line': {'color': "red", 'width': 4},
'thickness': 0.75,
'value': 301}}))
figaqi.update_layout(font = {'color': "black", 'family': "Arial", 'size': 30})
if value == 'tab-ataglance':
return html.Div([
html.H1([dcc.Graph(figure=figaqi, style={'display': 'inline-block'}),
dcc.Graph(figure=fighumidity, style={'display': 'inline-block'})]),
])
#Create layered tabs for histographs
@app.callback(Output('tabs-graph','children'),
Input('tabs','value'),
Input('interval_component','n_intervals'),
)
#create smaller tabs, each with graph
def update_tabs(value,n_intervals):
#PM-----------------------------------------------------------------------------------------------------
figpm = go.Figure()
#connect to database
conn=sqlite3.connect('MUAIRtestdatabase', check_same_thread=False)
#figure creation def
def update_fig(n_intervals,particle):
#connect to dataframe
dfpm= pandas.read_sql_query("SELECT * FROM totalpm", conn)
figpm=px.line( dfpm,x='time', y='avg', color='particle', line_shape="spline")
#display updated figure
return figpm
figpm.update_layout(
title_text="Particulate Matter Histograph"
)
#create individual PM graphs
figpm1 = go.Figure()
figpm25 = go.Figure()
figpm4 = go.Figure()
figpm10 = go.Figure()
#connect to database and create base figure
conn=sqlite3.connect('MUAIRtestdatabase', check_same_thread=False)
dfpm1= pandas.read_sql_query("SELECT * FROM pm1", conn)
dfpm25= pandas.read_sql_query("SELECT * FROM pm2p5", conn)
dfpm4= pandas.read_sql_query("SELECT * FROM pm4", conn)
dfpm10= pandas.read_sql_query("SELECT * FROM pm10", conn)
#pm1----------
figpm1=px.line( dfpm1,x='time', y='avg', line_shape="spline")
figpm1.update_layout(
title_text="PM1"
)
# Add range slider
figpm1.update_layout(
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=7,
label="1w",
step="day",
stepmode="backward"),
dict(count=1,
label="1m",
step="month",
stepmode="backward"),
dict(count=6,
label="6m",
step="month",
stepmode="backward"),
dict(count=1,
label="1y",
step="year",
stepmode="backward"),
dict(step="all")
])
),
rangeslider=dict(
visible=True
),
type="date"
)
)
#pm2.5----
figpm25=px.line( dfpm25,x='time', y='avg', line_shape="spline")
figpm25.update_layout(
title_text="PM2.5"
)
# Add range slider
figpm25.update_layout(
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=7,
label="1w",
step="day",
stepmode="backward"),
dict(count=1,
label="1m",
step="month",
stepmode="backward"),
dict(count=6,
label="6m",
step="month",
stepmode="backward"),
dict(count=1,
label="1y",
step="year",
stepmode="backward"),
dict(step="all")
])
),
rangeslider=dict(
visible=True
),
type="date"
)
)
#pm4----
figpm4=px.line( dfpm4,x='time', y='avg', line_shape="spline")
figpm4.update_layout(
title_text="PM4"
)
# Add range slider
figpm25.update_layout(
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=7,
label="1w",
step="day",
stepmode="backward"),
dict(count=1,
label="1m",
step="month",
stepmode="backward"),
dict(count=6,
label="6m",
step="month",
stepmode="backward"),
dict(count=1,
label="1y",
step="year",
stepmode="backward"),
dict(step="all")
])
),
rangeslider=dict(
visible=True
),
type="date"
)
)
#pm10-----
figpm10=px.line( dfpm10,x='time', y='avg', line_shape="spline")
figpm10.update_layout(
title_text="PM10"
)
# Add range slider
figpm10.update_layout(
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=7,
label="1w",
step="day",
stepmode="backward"),
dict(count=1,
label="1m",
step="month",
stepmode="backward"),
dict(count=6,
label="6m",
step="month",
stepmode="backward"),
dict(count=1,
label="1y",
step="year",
stepmode="backward"),
dict(step="all")
])
),
rangeslider=dict(
visible=True
),
type="date"
)
)
#NOX--------------------------------------------------------------------------------------------------
fignox = go.Figure()
#connect to database and create base figure
conn=sqlite3.connect('MUAIRtestdatabase', check_same_thread=False)
dfnox= pandas.read_sql_query("SELECT * FROM nox", conn)
fignox=px.line( dfnox,x='time', y='avg', line_shape="spline")
fignox.update_layout(
title_text="NOX Index Histograph"
)
# Add range slider
fignox.update_layout(
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=7,
label="1w",
step="day",
stepmode="backward"),
dict(count=1,
label="1m",
step="month",
stepmode="backward"),
dict(count=6,
label="6m",
step="month",
stepmode="backward"),
dict(count=1,
label="1y",
step="year",
stepmode="backward"),
dict(step="all")
])
),
rangeslider=dict(
visible=True
),
type="date"
)
)
#VOC---------------------------------------------------------------------------------------------------
figvoc = go.Figure()
#connect to database and create figure
conn=sqlite3.connect('MUAIRtestdatabase', check_same_thread=False)
dfvoc= pandas.read_sql_query("SELECT * FROM voc", conn)
figvoc=px.line( dfvoc,x='time', y='avg', line_shape="spline")
figvoc.update_layout(
title_text="VOC Index Histograph"
)
# Add range slider
figvoc.update_layout(
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=7,
label="1w",
step="day",
stepmode="backward"),
dict(count=1,
label="1m",
step="month",
stepmode="backward"),
dict(count=6,
label="6m",
step="month",
stepmode="backward"),
dict(count=1,
label="1y",
step="year",
stepmode="backward"),
dict(step="all")
])
),
rangeslider=dict(
visible=True,
),
type="date"
)
)
#if the graphs tab is selected...return certain graphs based on sub graphs
if value == 'tabs-anygraph':
return dcc.Tabs(id='graph-tabs', value='PMGraph', children=[
dcc.Tab(label='Particulate Matter: PM1', id='pm1graph', value='PM1Graph', children=[
html.Div(
html.H1([dcc.Graph(figure=figpm1, id='figpm1graph'),
])
)
]),
dcc.Tab(label='Particulate Matter: PM2.5', id='pm25graph', value='PM25Graph', children=[
html.Div(
html.H1([dcc.Graph(figure=figpm25, id='figpm25graph'),
])
)
]),
dcc.Tab(label='Particulate Matter:PM4', id='pm4graph', value='PM4Graph', children=[
html.Div(
html.H1([dcc.Graph(figure=figpm4, id='figpm4graph'),
])
)
]),
dcc.Tab(label='Particulate Matter: PM10', id='pm10graph', value='PM10Graph', children=[
html.Div(
html.H1([dcc.Graph(figure=figpm10, id='figpm10graph'),
])
)
]),
dcc.Tab(label='NOX Index', id='noxgraph', value='NOXGraph', children=[
html.Div(
html.H1(dcc.Graph(figure=fignox, id='fignoxgraph')))
]),
dcc.Tab(label='VOC Index', id='vocgraph', value='VOCGraph', children=[
html.Div([
html.H1(dcc.Graph(figure=figvoc, id='figvocgraph')
),
])
])
])
#debug app, eventually will be deploying app
if __name__ == '__main__':
app.run(debug=True)
The goal of this UI is to fit on an air quality sensor (the actual project we worked on most of the year), and display our data. Therefore it had to be usable with a 5" touchscreen, so I avoided any scrolling and opted to use tabs for most of the organization. Here’s what it looks like so far. Currently working on making it prettier, but learning HTML to jazz it up a bit is a whole thing in itself.
Can’t express how much this forum helped me. Cant wait to learn more!