Dahsboard app for viewing data

A simple dashboard was created using the Dash library in python in order to view the collected data. The dashboard consists of 3 pages displaying different information

General structure

The app is broken down into 4 pieces. A landing page when the app is accessed and pages to view recent data, historical data and images.

Each page is defined in its own file in a folder called pages. To include the page, it has to be added by the built in register page function, which takes in a page name and what number in the sequence it is.

dash.register_page(__name__, name="Page_name", order=0)

The page file is defined as normal by defining the page layout.

 1layout = html.Div(
 2    children=[
 3        html.H1(
 4            id='header_text',
 5            children='Text to display'),
 6
 7        html.Div(
 8            children=[
 9                dcc.Graph(
10                    id='graph name',
11                    figure=createmainSubplot(df)
12                ),
13
14                dcc.Interval(
15                    id='update_interval',
16                    interval=4000,
17                    n_intervals=0
18                )
19            ]
20        )
21    ]
22)

The main program initializes the app. Here a div container is created that holds all the previously defined pages as other div containers. This is where the buttons to access other pages are created. Finally, it displays the currently selected page.

 1app = Dash(__name__, use_pages=True)
 2
 3app.layout = html.Div(
 4    children=[
 5        html.H1(
 6            children=[
 7                "MASXXX DataView"
 8            ],
 9            style={
10                'textAlign':'center'
11            }
12        ),
13
14        #Create links to other pages
15        html.Div(
16            children=[
17                html.Div(
18                    children=[
19                        dcc.Link(html.Button(page['name'], style={'width':'100px'}), href=page["relative_path"], style={'padding':'5px'}) for page in dash.page_registry.values() if page['name'] != 'Home'
20                    ],
21                )
22            ]
23        ),
24
25        #Show the contents of the current page
26        dash.page_container,
27
28    ]
29)

Landing page

The landing page is the first page shown when the app is accessed. Here, the user is told a little about what the pages contain and are presented with buttons to access each individual page.

 1dash.register_page(__name__, name="Home", path='/')
 2
 3
 4layout = html.Div(
 5    children=[
 6        html.H1(
 7            children='Wecome to the MASXXX data view app',
 8            style={'textAlign':'center'}
 9        ),
10
11        html.Div(
12            children="In the top left corner you find buttons that take you to individual pages to view specific data.",
13            style={'textAlign':'center'}
14        ),
15
16        html.Img(src='assets/3.jpg', style={'width':'50%'})
17    ]
18)

Recent data page

The recent data page displays all 8 measurements taken the past 90 minutes.

 1dash.register_page(__name__, name="Recent Data", order=0)
 2
 3layout = html.Div(
 4    children=[
 5        html.H1(
 6            id='header_text',
 7            children='Recent Data'),
 8
 9        html.Div(
10            children=[
11                dcc.Graph(
12                    id='recent_data_subplots',
13                    figure=createmainSubplot(df)
14                ),
15
16                dcc.Interval(
17                    id='update_interval',
18                    interval=4000,
19                    n_intervals=0
20                )
21            ]
22        )
23    ]
24)

This page has a timed callback that fetches new data evert 5 minutes, then refreshes the graphs.

 1@callback(
 2Output(component_id='recent_data_subplots', component_property='figure'),
 3Input(component_id='update_interval', component_property='n_intervals')
 4)
 5def updateRecentData(n_intervals):
 6    #Reload dataframe
 7    df = pd.read_csv('server/data.csv')
 8
 9    dfRows = df.shape[0]
10    if dfRows > 400:
11        df = df[dfRows - 400 : dfRows]
12
13    return createmainSubplot(df)

The graphs are drawn with a custom function that uses the Plotly library in order to create a custom subplot. The new subplot figure is returned to the callback function, which returns it to the graph element, replacing the previous graph.

 1def createmainSubplot(df):
 2    mainSubplotFigure = make_subplots(rows=4, cols=2, subplot_titles=['Soil', 'Humidity', 'Temperature Inside', 'Temperature Outside', 'eCO2', 'TVOC', 'NA', 'PH'])
 3    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['Soil']),            row=1, col=1)
 4    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['Humidity']),        row=1, col=2)
 5    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['Temperature_in']),  row=2, col=1)
 6    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['Temperature_out']), row=2, col=2)
 7    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['eCO2']),            row=3, col=1)
 8    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['TVOC']),            row=3, col=2)
 9    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['TVOC']),            row=4, col=1)
10    mainSubplotFigure.add_trace(go.Scatter(x=df['time'], y=df['PH']),              row=4, col=2)
11
12    mainSubplotFigure.update_yaxes(title_text="",     row=1, col=1) #Soil 1 plot
13    mainSubplotFigure.update_yaxes(title_text="%",    row=1, col=2) #Humidity plot
14    mainSubplotFigure.update_yaxes(title_text="°C",   row=2, col=1) #Temp inside plot
15    mainSubplotFigure.update_yaxes(title_text="°C",   row=2, col=2) #Temp outside
16    mainSubplotFigure.update_yaxes(title_text="PPM",  row=3, col=1) #CO2
17    mainSubplotFigure.update_yaxes(title_text="PH",   row=3, col=2) #TVOC
18    mainSubplotFigure.update_yaxes(title_text="PPM",  row=4, col=1) #CO2
19    mainSubplotFigure.update_yaxes(title_text="",     row=4, col=2) #TVOC
20
21    mainSubplotFigure.update_layout(showlegend=False)
22    mainSubplotFigure.update_layout(height=600)
23
24    return mainSubplotFigure

Historical data page

This page shows data over a longer time frame than the recent data page. The data to view is selected with a dropdown menu, and the appropriate data is loaded in and presented.

 1df = pd.read_csv('server/data.csv')
 2
 3dash.register_page(__name__, name="Historical Data", order=1)
 4
 5layout = html.Div(
 6    children=[
 7        html.H1('Historical Data'),
 8
 9        html.Div(
10            "This is the page for viewing selected historical data"
11        ),
12
13        html.Div(
14            children=[
15                dcc.Dropdown(
16                    id='selection_dropdown',
17                    options=df.columns[:-1],
18                    value="Humidity"
19                )
20            ]
21        ),
22
23        html.Div(
24            id='graph_container',
25            children=[
26                dcc.Graph(
27                    id="historical_graph",
28                    figure=px.line(x = df['time'], y = df['Humidity'])
29                )
30            ]
31        )
32    ]
33)

This page also uses a callback function. Based on what data category is selected from the dropdown, the appropriate column is selected from the dataframe and displayed. If the selection is left empty, humudidy is displayed by default.

1@callback(
2Output(component_id='historical_graph', component_property='figure'),
3Input(component_id='selection_dropdown', component_property='value')
4)
5def updateGraph(selectedColumn):
6    if selectedColumn is None:
7        return px.line(x = df['time'], y = df['Humidity'])
8    return px.line(x = df['time'], y = df[selectedColumn])

Pictures page

The final page is for displaying pictures of the plants as they grow. One picture should be taken per day, and a slider is used to select the day. By sliding the slider back and forth, a timelapse of the plants growing will show.

 1dash.register_page(__name__, name="Pictures", order=3)
 2
 3layout = html.Div(
 4    children=[
 5        html.H1('Pictures'),
 6        html.H3('I propose a slider to select a date, and view the picture taken that day'),
 7
 8        dcc.Slider(
 9            id='date_slider',
10            min=0,
11            max=110,
12            value=0,
13            step=1,
14            updatemode='drag'
15        ),
16
17        html.Div(
18            children=[
19                html.Img(
20                    id='image',
21                    src='assets/plant_pictures/0.png'
22                ),
23            ],
24            style={
25                'display':'inline-block',
26                'width':'100%'
27            }
28
29        ),
30
31
32    ]
33)

The callback retrieves the current number on the slider and loads in the corresponding image.

1@callback(
2Output(component_id='image', component_property='src'),
3Input(component_id='date_slider', component_property='value')
4)
5def updateImage(imgNumber):
6    return f"assets/plant_pictures/{imgNumber}.png"