diff --git a/README.md b/README.md
index 4c2e69d..fd09fdc 100644
--- a/README.md
+++ b/README.md
@@ -20,10 +20,14 @@ python dashboard.py
Then navigate to [http://127.0.0.1:8050/](http://127.0.0.1:8050/) in your browser to see the graphs.
-**Note:** For component pieces, see `test_components` folder; they are run similarly.
-
### Preview
-
+
+#### Histogram View
+
+
+#### Map View
+
+
### Running Tests
diff --git a/components/README.md b/components/README.md
deleted file mode 100644
index 662810e..0000000
--- a/components/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Dashboard Prototype Test components
-Prototype data dashboard components (histogram, histogram + pie chart, and image selector) using the [Cuthill Gold Standard Dataset](https://datacommons.tdai.osu.edu/dataset.xhtml?persistentId=doi:10.5072/FK2/GZYWNV&version=DRAFT), which was processed from Cuthill, et. al. (original dataset available at [doi:10.5061/dryad.2hp1978](https://doi.org/10.5061/dryad.2hp1978)).
-
-
-### How it works
-
-Create and activate a new (python) virtual environment.
-Then install the required packages (if using `conda`, first run `conda install pip`):
-
-```
-pip install -r requirements.txt
-```
-
-and run
-
-```
-python prototype-multiplot.py
-```
-
-Then navigate to [http://127.0.0.1:8050/](http://127.0.0.1:8050/) in your browser to see the graphs (histogram and pie chart).
-
-
-**Note:** `proto-img-only.py` and `prototype_histogram.py` can be run in the same manner, but will only produce the image selector or histogram, respectively.
-
-### Preview
-
diff --git a/components/divs.py b/components/divs.py
new file mode 100644
index 0000000..698d59e
--- /dev/null
+++ b/components/divs.py
@@ -0,0 +1,110 @@
+from dash import html, dcc
+
+def get_hist_div(cat_list, sort_list, H4_style, div_style):
+ '''
+ Function to generate the histogram options section of the dashboard, including button to select 'Map View'.
+ Provides choice of variables for distribution and to color by, with options for order to sort x-axis.
+
+ Parameters:
+ -----------
+ cat_list - List of categorical variables to be used for distribution and color-by options.
+ sort_list - List of options for sorting x-axis of histogram.
+ H4_style - Style setting for html Header 4.
+ div_style - Style setting for div containers.
+
+ Returns:
+ --------
+ hist_div - HTML Div containing all user options for histogram (variable for distribution, coloring, and order to sort x-axis), plus and 'Map View' button.
+
+ '''
+ hist_div = [
+ html.Div([
+ html.H4("Show me the distribution of ...", style = H4_style),
+ # Add dropdown options
+ # x-axis (feature) distribution options: 'Subspecies', 'Additional Taxa Information', 'Locality'
+ dcc.RadioItems(cat_list[:2] + cat_list[5:],
+ 'Subspecies',
+ id = 'x-variable')
+ ], style = div_style
+ ),
+
+ html.Div([
+ html.H4("Colored by ...", style = H4_style),
+ #select color-by option: 'View', 'Sex', 'Hybrid Status'
+ dcc.RadioItems(cat_list[2:-2],
+ 'View',
+ id = 'color-by')
+ ], style = div_style
+ ),
+
+ html.Div([
+ html.H4("Sort distribution ", style = {'color': 'MidnightBlue', 'margin-top' : 10, 'margin-bottom' : 10}),
+ dcc.RadioItems(sort_list,
+ 'alpha',
+ id = 'sort-by',
+ inline = True)
+ ], style = div_style
+ ),
+ html.Div([
+ # Button to switch to Map View
+ html.Button("Show Map View",
+ id = 'dist-view-btn',
+ n_clicks = 0)
+ ], style = div_style
+ )
+ ]
+ return hist_div
+
+def get_map_div(cat_list, H4_style, div_style):
+ '''
+ Function to generate the mapping options section of the dashboard.
+ Provides choice of variables to color by and button to switch back to histogram ('Show Histogram').
+
+ Parameters:
+ -----------
+ cat_list - List of categorical variables to be used for color-by options.
+ H4_style - Style setting for html Header 4.
+ div_style - Style setting for div containers.
+
+ Returns:
+ --------
+ map_div - HTML Div containing all user options for map (variables for coloring) and 'Show Histogram' button.
+
+ '''
+ map_div = [
+ html.Div([
+ html.H4('''
+ This map shows the distribution of samples by locality,
+ where the size of the dots is determined by the total number of samples at that location.
+ ''',
+ id = 'x-variable', #label to avoid nonexistent callback variable
+ style = {'color': 'MidnightBlue', 'margin-left': 20, 'margin-right': 20}
+ )
+ ], style = {'width': '48%', 'display': 'inline-block', 'vertical-align': 'bottom'}
+ ),
+
+ html.Div([
+ html.H4("Colored by ...", style = H4_style),
+ #select color-by option: 'Species', 'Subspecies', 'View', 'Sex', 'Hybrid Status', 'Additional Taxa Information', 'Locality'
+ dcc.RadioItems(cat_list,
+ 'View',
+ id = 'color-by',
+ style = {'padding-right': '20%', 'display': 'inline-flex', 'flex-wrap': 'wrap', 'flex-direction': 'row', 'justify-content': 'space-between'})
+ ], style = {'width': '48%', 'display': 'inline-block', 'margin-bottom': 20}
+ ),
+
+ html.Div([
+ ],
+ id = 'sort-by', #label sort-by box to avoid non-existent label and generate box so button doesn't move between views
+ style = div_style
+ ),
+ html.Div([
+ # Distribution View Type Button
+ html.Button("Show Histogram",
+ id = 'dist-view-btn',
+ n_clicks = 0)
+ ], style = div_style
+ )
+ ]
+
+ return map_div
diff --git a/components/graphs.py b/components/graphs.py
index 9f1cc26..ce178e3 100644
--- a/components/graphs.py
+++ b/components/graphs.py
@@ -1,26 +1,57 @@
import plotly.express as px
+def make_hist_plot(df, x_var, color_by, sort_by):
+ '''
+ Generates interactive histogram of selected variable, with option of properties to color by and order in which to sort.
+
+ Parameters:
+ -----------
+ df - DataFrame of specimens.
+ x_var - Variable to plot distribution.
+ color_by - Property to color the plot by.
+ sort_by - Ordering of bar charts (Alphabetical, Ascending, or Descending).
-def make_map(df):
+ Returns:
+ --------
+ fig - Histogram of the distribution of the requested variable.
'''
- Generates interactive graph of Species and Subspecies by location.
+ if sort_by == 'alpha':
+ fig = px.histogram(df.sort_values(x_var),
+ x = x_var,
+ color = color_by,
+ color_discrete_sequence = px.colors.qualitative.Bold)
+ else:
+ fig = px.histogram(df,
+ x = x_var,
+ color = color_by,
+ color_discrete_sequence = px.colors.qualitative.Bold).update_xaxes(categoryorder = sort_by)
+
+ fig.update_layout(title = {'text': f'Distribution of {x_var} Colored by {color_by}'})
+
+ return fig
+
+def make_map(df, color_by):
+ '''
+ Generates interactive map of species and subspecies by location.
Parameters:
-----------
- df - dataframe of specimens
+ df - DataFrame of specimens.
+ color_by - Selected categorical variable by which to color.
Returns:
--------
- fig - Map of their locations
+ fig - Map of their locations.
'''
fig = px.scatter_geo(df,
lat = df.lat,
lon = df.lon,
projection = "natural earth",
- hover_data = ["Species", "Subspecies"],
- size = df.samples_at_locality,
- color = "Subspecies",
- color_discrete_sequence = px.colors.qualitative.Bold)
+ custom_data = ["Samples_at_locality", "Species_at_locality", "Subspecies_at_locality"],
+ size = df.Samples_at_locality,
+ color = color_by,
+ color_discrete_sequence = px.colors.qualitative.Bold,
+ title = "Distribution of Samples")
fig.update_geos(fitbounds = "locations",
showcountries = True, countrycolor = "Grey",
@@ -29,4 +60,40 @@ def make_map(df):
showland = True, landcolor = "wheat",
showocean = True, oceancolor = "LightBlue")
+ fig.update_traces(hovertemplate =
+ "Latitude: %{lat}
"+
+ "Longitude: %{lon}
" +
+ "Samples at lat/lon: %{customdata[0]}
" +
+ "Species at lat/lon: %{customdata[1]}
" +
+ "Subspecies at lat/lon: %{customdata[2]}
"
+ )
+
return fig
+
+def make_pie_plot(df, var):
+ '''
+ Generates interactive pie chart of dataset specimens with option of properties to color by.
+
+ Parameters:
+ -----------
+ df - DataFrame of specimens.
+ var - Selected categorical variable by which to color.
+
+ Returns:
+ --------
+ fig - Pie chart of the percentage breakdown of the `var` samples in the dataset.
+ '''
+ if(var == 'Subspecies'):
+ pie_fig = px.pie(df,
+ names = var,
+ color_discrete_sequence = px.colors.qualitative.Bold,
+ hover_data = ['Species'])
+ else:
+ pie_fig = px.pie(df,
+ names = var,
+ color_discrete_sequence = px.colors.qualitative.Bold)
+ pie_fig.update_traces(textposition = 'inside', textinfo = 'percent+label')
+
+ pie_fig.update_layout(title = {'text': f'Percentage Breakdown of {var}'})
+
+ return pie_fig
diff --git a/components/proto-img-only.py b/components/proto-img-only.py
deleted file mode 100644
index 7b1c476..0000000
--- a/components/proto-img-only.py
+++ /dev/null
@@ -1,139 +0,0 @@
-import pandas as pd
-from dash import Dash, html, dcc, Input, Output, State
-from dash.exceptions import PreventUpdate
-
-from query import get_species_options
-
-df = pd.read_csv("test_data/Hoyal_Cuthill_GoldStandard_metadata_cleaned.csv")
-
-app = Dash(__name__)
-
-all_species = get_species_options(df)
-
-app.layout = html.Div([
- html.Div([
- html.H4("Show me sample images of ...", style = {"color":"MidnightBlue", 'marginBottom' : 10}),
- #select Species/Subspecies to view (defaul to Any)
- # Note: these should be the same type to interact properly, first must not be clearable
- dcc.Dropdown(options = list(all_species.keys()),
- value = 'Any',
- id = 'species-show',
- clearable = False),
- html.Hr(),
- dcc.Dropdown(
- multi = True,
- id = 'subspecies-show',
- placeholder = 'Select Subspecies to View'),
- # Further Refine by Features
- html.H4("that are ...", style = {"color":"MidnightBlue", 'marginBottom' : 10}),
- html.Div([
- dcc.Checklist(df.Sex.unique(),
- df.Sex.unique()[0:2],
- id = 'which-sex')],
- style = {'width': '24%', 'display': 'inline-block'}
- ),
- html.Div([
- dcc.Checklist(df.View.unique(),
- df.View.unique()[0:2],
- id = 'which-view')],
- style = {'width': '24%', 'display': 'inline-block'}
- ),
- html.Div([
- dcc.Checklist(df.hybrid_stat.unique(),
- df.hybrid_stat.unique()[0:2],
- id = 'hybrid?')],
- style = {'width': '24%', 'display': 'inline-block'}
- )
- ], id = 'dropdown-images'),
-
- html.Hr(),
-
- #Button to activate the callback
- html.Button('Display Images',
- id = 'display-img',
- n_clicks = 0),
-
- # Add some space after the button
- html.Br(),
- html.Br(),
- html.Br(),
- html.Br(),
-
- # Image Should appear
- html.Div(id = 'image-1')
-
-])
-
-# Callback for Image Species Selection
-@app.callback(
- Output(component_id = 'subspecies-show', component_property= 'options'),
- Input(component_id = 'species-show', component_property = 'value')
-)
-
-def set_subspecies_options(selected_species):
- # Set subspecies options based on selected species
- return [{'label': i, 'value': i} for i in all_species[selected_species]]
-
-# Callback for Image Subspecies Selection
-@app.callback(
- Output(component_id = 'subspecies-show', component_property= 'value'),
- Input(component_id = 'subspecies-show', component_property = 'options')
-)
-
-def set_subspecies_value(available_options):
- # Collect selected subspecies
- return available_options[0]['value']
-
-@app.callback(
- Output('image-1', 'children'),
- Input('display-img', 'n_clicks'),
- #State('species-show', 'value'),
- State('subspecies-show', 'value'),
- State('which-view', 'value'),
- State('which-sex', 'value'),
- State('hybrid?', 'value'),
- prevent_initial_call = True
-)
-
-# Retrieve a single image
-def get_image(n_clicks, subspecies, view, sex, hybrid):
- #if n_clicks is None:
- # raise PreventUpdate
- # else:
- filename = get_filename(subspecies, view, sex, hybrid)
- if filename == 0:
- #print("No Such Images. Please make another selection.")
- return html.H4("No Such Images. Please make another selection.",
- style = {"color":"MidnightBlue"})
- if 'D_lowres' in filename:
- image_directory = "dorsal_images/"
- else:
- image_directory = "ventral_images/"
- #remove 'tif' from filename and replace with 'png' in url
- image_path = "https://github.com/Imageomics/dashboard-prototype/blob/feature/image-options/test_data/images/" + image_directory + filename[:-3] + "png?raw=true"
- return html.Img(src = image_path)
-
-def get_filename(subspecies, view, sex, hybrid):
- #filter df by subspecies, then view, sex and hybrid
- #return filenames for 7 randomly selected images from the filtered dataset
- #check for Any-Melpomene, Any-Erato, or Any (general)
- if 'Any' in subspecies:
- if subspecies == 'Any':
- df_sub = df.copy()
- else:
- subspecies = subspecies.split('-')[1].lower()
- df_sub = df.loc[df.Subspecies == subspecies].copy()
- else:
- df_sub = df.loc[df.Subspecies.isin(subspecies)].copy()
- df_sub = df_sub.loc[df_sub.View.isin(view)]
- df_sub = df_sub.loc[df_sub.Sex.isin(sex)]
- df_filtered = df_sub.loc[df_sub.hybrid_stat.isin(hybrid)]
- if len(df_filtered) > 0:
- filename = df_filtered.sample().Image_filename.astype('string').values[0]
- return filename
- else:
- return 0
-
-
-if __name__ == '__main__':
- app.run_server(debug=True)
diff --git a/components/prototype-map.py b/components/prototype-map.py
deleted file mode 100644
index 69f7ff9..0000000
--- a/components/prototype-map.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import pandas as pd
-from dash import Dash, html, dcc
-from graphs import make_map
-
-df = pd.read_csv("test_data/Hoyal_Cuthill_GoldStandard_metadata_cleaned.csv")
-df["samples_at_locality"] = df.locality.map(df.locality.value_counts()/2)
-
-app = Dash(__name__)
-
-app.layout = html.Div([
- html.H1("Distribution of Samples", style = {'textAlign': 'center', 'color': 'MidnightBlue'}),
-
- # Add empty plot for map
- dcc.Graph(figure = make_map(df),
- id = 'map')
-])
-
-if __name__ == '__main__':
- app.run_server(debug=True)
diff --git a/components/prototype-multiplot.py b/components/prototype-multiplot.py
deleted file mode 100644
index 71d608b..0000000
--- a/components/prototype-multiplot.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import pandas as pd
-import plotly.express as px
-from dash import Dash, html, dcc, Input, Output
-
-df = pd.read_csv("test_data/Hoyal_Cuthill_GoldStandard_metadata_cleaned.csv")
-
-app = Dash(__name__)
-
-app.layout = html.Div([
- html.H1("Cuthill Data Distribution Statistics", style = {'textAlign': 'center', 'color': 'MidnightBlue'}),
-
- #Distribution Options
- html.Div([
- html.Div([
- html.H4("Show me the distribution of ...", style = {"color":"MidnightBlue", 'margin-bottom' : 10}),
- # Add dropdown options
- # x-axis (feature) distribution options: 'Subspecies', 'Additional Taxa Information', 'Locality'
- dcc.RadioItems([
- {'label': 'Subspecies', 'value': 'Subspecies'},
- {'label': 'Additional Taxa Information', 'value':'addit_taxa_info'},
- {'label': 'Locality', 'value': 'locality'}],
- 'Subspecies',
- id = 'x-variable')
- ], style = {'width': '48%', 'display': 'inline-block'}
- ),
-
- html.Div([
- html.H4("Colored by ...", style = {'color': 'MidnightBlue', 'margin-bottom' : 10}),
- #select color-by option: 'View', 'Sex', 'Hybrid Status'
- dcc.RadioItems([
- {'label':'View', 'value': 'View'},
- {'label': 'Sex', 'value': 'Sex'},
- {'label': 'Hybrid Status', 'value':'hybrid_stat'}],
- 'View',
- id = 'color-by')
- ], style = {'width': '48%', 'display': 'inline-block'}
- ),
- #html.Br(),
- html.H4("Sort distribution ", style = {'color': 'MidnightBlue', 'margin-top' : 10, 'margin-bottom' : 10}),
- dcc.RadioItems([
- {'label': 'Alphabetical', 'value': 'alpha'},
- {'label': 'Ascending', 'value': 'sum ascending'},
- {'label': 'Descending', 'value': 'sum descending'}],
- 'alpha',
- id = 'sort-by',
- inline = True),
- ], style = {'width': '48%', 'display': 'inline-block'}
- ),
-
- #pie chart options
- html.Div([
- html.H4("Show me the Percentage Breakdown of ...", style = {'color': 'MidnightBlue', 'margin-bottom' : 10}),
- dcc.RadioItems([
- {'label': 'Species', 'value': 'Species'},
- {'label': 'Subspecies', 'value': 'Subspecies'},
- {'label':'View', 'value': 'View'},
- {'label': 'Sex', 'value': 'Sex'},
- {'label': 'Hybrid Status', 'value':'hybrid_stat'}],
- 'Species',
- id = 'prct-brkdwn'
- ),
- html.Br(),
- ], style = {'width': '48%', 'display': 'inline-block'}
- ),
-
- html.Br(),
- html.Br(),
-
- #Graphs
- html.Div([
- dcc.Graph(id = 'hist-plot')], style = {'width': '48%', 'display': 'inline-block'}),
- html.Div([
- dcc.Graph(id = 'pie-plot')], style = {'width': '48%', 'display': 'inline-block'})
-])
-
-@app.callback(
- #hist output
- Output(component_id='hist-plot', component_property='figure'),
- #input x_var
- Input(component_id='x-variable', component_property='value'),
- #input color_by
- Input(component_id='color-by', component_property='value'),
- #input sort_by
- Input(component_id='sort-by', component_property='value')
-)
-
-def make_hist_plot(x_var, color_by, sort_by):
- #generate histogram
- if sort_by == 'alpha':
- fig = px.histogram(df.sort_values(x_var),
- x = x_var,
- color = color_by,
- color_discrete_sequence = px.colors.qualitative.Bold)
- else:
- fig = px.histogram(df,
- x = x_var,
- color = color_by,
- color_discrete_sequence = px.colors.qualitative.Bold).update_xaxes(categoryorder = sort_by)
-
- fig.update_layout(title = {'text': f'Distribution of {x_var} Colored by {color_by}'})
-
- return fig
-
-@app.callback(
- #pie output
- Output(component_id='pie-plot', component_property='figure'),
- #pie input (var)
- Input(component_id='prct-brkdwn', component_property='value')
-)
-
-def make_pie_plot(var):
- #generate pie chart
- if(var == 'Subspecies'):
- pie_fig = px.pie(df,
- names = var,
- color_discrete_sequence = px.colors.qualitative.Bold,
- hover_data = ['Species'])
- else:
- pie_fig = px.pie(df,
- names = var,
- color_discrete_sequence = px.colors.qualitative.Bold)
- pie_fig.update_traces(textposition = 'inside', textinfo = 'percent+label')
-
- pie_fig.update_layout(title = {'text': f'Percentage Breakdown of {var}'})
-
- return pie_fig
-
-if __name__ == '__main__':
- app.run_server(debug=True)
\ No newline at end of file
diff --git a/components/prototype_histogram.py b/components/prototype_histogram.py
deleted file mode 100644
index 57f6881..0000000
--- a/components/prototype_histogram.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import pandas as pd
-import plotly.express as px
-from dash import Dash, html, dcc, Input, Output
-
-df = pd.read_csv("test_data/Hoyal_Cuthill_GoldStandard_metadata_cleaned.csv")
-
-app = Dash(__name__)
-
-app.layout = html.Div([
- html.H1("Choose your distribution", style = {"color":"blue", "justify-content": "center"}),
- # Add dropdown options
- # x-axis (feature) distribution options: 'Subspecies', 'Additional Taxa Information', 'Locality'
- dcc.Dropdown(['Subspecies', 'addit_taxa_info', 'locality'],
- 'Subspecies',
- id = 'x-variable'),
- html.Br(),
- #html.Div(id='dd1-output'),
- #select color-by option: 'View', 'Sex', 'Hybrid Status'
- dcc.RadioItems(['View', 'Sex', 'hybrid_stat'],
- 'View',
- id = 'color-by'),
- html.Br(),
- #html.Div(id = 'dd2-output'),
- # Add empty plot for histogram
- dcc.Graph(id = 'plot')
-])
-
-@app.callback(
- # output
- Output(component_id='plot', component_property='figure'),
- #input1
- Input(component_id='x-variable', component_property='value'),
- #input2
- Input(component_id='color-by', component_property='value')
-)
-
-def make_plot(x_var, color_by):
- fig = px.histogram(df.sort_values(x_var),
- x = x_var,
- color = color_by)
- return fig
-
-if __name__ == '__main__':
- app.run_server(debug=True)
\ No newline at end of file
diff --git a/components/query.py b/components/query.py
index 22bb9fc..94eae05 100644
--- a/components/query.py
+++ b/components/query.py
@@ -1,10 +1,57 @@
+import pandas as pd
import numpy as np
+from dash import html
+
+IMAGES_BASE_URL = "https://github.com/Imageomics/dashboard-prototype/raw/main/test_data/images/"
# Helper functions for Dashboard
+def get_data():
+ '''
+ Function to read in DataFrame and perform required manipulations:
+ - add 'lat-lon', `Samples_at_locality`, 'Species_at_locality', and 'Subspecies_at_locality' columns.
+ - make list of categorical columns.
+
+ Returns:
+ --------
+ df - DataFrame of CSV with added column of number of samples collected at each lat-lon pair.
+ cat_list - List of categorical variables for RadioItems (pie chart and map).
+
+ '''
+ df = pd.read_csv("test_data/Hoyal_Cuthill_GoldStandard_metadata_cleaned.csv")
+ df['lat-lon'] = df['lat'].astype(str) + '|' + df['lon'].astype(str)
+ df["Samples_at_locality"] = df['lat-lon'].map(df['lat-lon'].value_counts()/2)
+
+ # Count and record number of species and subspecies at each lat-lon
+ for lat_lon in df['lat-lon']:
+ species_list = ['{}'.format(i) for i in df.loc[df['lat-lon'] == lat_lon]['Species'].unique()]
+ subspecies_list = ['{}'.format(i) for i in df.loc[df['lat-lon'] == lat_lon]['Subspecies'].unique()]
+ df.loc[df['lat-lon'] == lat_lon, 'Species_at_locality'] = ", ".join(species_list)
+ df.loc[df['lat-lon'] == lat_lon, 'Subspecies_at_locality'] = ", ".join(subspecies_list)
+
+ # Dictionary of categorical values for graphing options
+ cat_list = [{'label': 'Species', 'value': 'Species'},
+ {'label': 'Subspecies', 'value': 'Subspecies'},
+ {'label':'View', 'value': 'View'},
+ {'label': 'Sex', 'value': 'Sex'},
+ {'label': 'Hybrid Status', 'value':'hybrid_stat'},
+ {'label': 'Additional Taxa Information', 'value':'addit_taxa_info'},
+ {'label': 'Locality', 'value': 'locality'}]
+
+ return df, cat_list
+
def get_species_options(df):
'''
- Function to pull in dataFrame and produce a dictionary of species options (Melpomene, Erato, and Any)
+ Function to pull in DataFrame and produce a dictionary of species options (Melpomene, Erato, and Any)
+
+ Parameters:
+ -----------
+ df - DataFrame with image metadata.
+
+ Returns:
+ --------
+ all_species - Dictionary of all potential species options and their subspecies.
+
'''
melpomene_subspecies = df.loc[df.Species == 'melpomene', 'Subspecies'].unique()
erato_subspecies = df.loc[df.Species == 'erato', 'Subspecies'].unique()
@@ -16,4 +63,81 @@ def get_species_options(df):
'Erato' : erato_subspecies,
'Any' : all_subspecies
}
- return all_species
\ No newline at end of file
+ return all_species
+
+# Retrieve selected number of images
+
+def get_images(df, subspecies, view, sex, hybrid, num_images):
+ '''
+ Function to retrieve the user-selected number of images.
+
+ Parameters:
+ -----------
+ df - DataFrame with image metadata.
+ subspecies - String. Subspecies of specimen selected by the user.
+ view - String. View of specimen selected by the user.
+ sex - String. Sex of specimen selected by the user.
+ hybrid - String. Hybrid status of specimen selected by the user.
+ num_images - Integer. Number of images requested by the user.
+
+ Returns:
+ --------
+ Imgs - List of html image elements with `src` element pointing to paths for the requested number of images matching given parameters.
+ Returns html header4 "No Such Images. Please make another selection." if no images matching parameters exist.
+ '''
+ filenames = get_filenames(df, subspecies, view, sex, hybrid, num_images)
+ if filenames == 0:
+ #print("No Such Images. Please make another selection.")
+ return html.H4("No Such Images. Please make another selection.",
+ style = {"color":"MidnightBlue"})
+ Imgs = []
+ for filename in filenames:
+ if 'D_lowres' in filename:
+ image_directory = "dorsal_images/"
+ else:
+ image_directory = "ventral_images/"
+ #remove 'tif' from filename and replace with 'png' in url
+ image_path = IMAGES_BASE_URL + image_directory + filename[:-3] + "png?raw=true"
+ Imgs.append(html.Img(src = image_path))
+ return Imgs
+
+def get_filenames(df, subspecies, view, sex, hybrid, num_images):
+ '''
+ Funtion to randomly select the given number of filenames for images adhering to specified filters.
+
+ Parameters:
+ -----------
+ df - DataFrame with image metadata.
+ subspecies - String. Subspecies of specimen selected by the user.
+ view - String. View of specimen selected by the user.
+ sex - String. Sex of specimen selected by the user.
+ hybrid - String. Hybrid status of specimen selected by the user.
+ num_images - Integer. Number of images requested by the user. Defaults to 1 if no selection.
+
+ Returns:
+ --------
+ filenames or 0 - List of filenames meeting specified conditions (the lesser of the requested amount or number available).
+ Returns 0 if no matching values.
+ '''
+ if 'Any' in subspecies:
+ if subspecies == 'Any':
+ df_sub = df.copy()
+ else:
+ subspecies = subspecies.split('-')[1].lower()
+ df_sub = df.loc[df.Subspecies == subspecies].copy()
+ else:
+ df_sub = df.loc[df.Subspecies.isin(subspecies)].copy()
+ df_sub = df_sub.loc[df_sub.View.isin(view)]
+ df_sub = df_sub.loc[df_sub.Sex.isin(sex)]
+ df_filtered = df_sub.loc[df_sub.hybrid_stat.isin(hybrid)]
+ max_imgs = len(df_filtered)
+ if max_imgs > 0:
+ if num_images == None:
+ num = 1
+ else:
+ num = min(num_images, max_imgs)
+ filenames = df_filtered.sample(num).Image_filename.astype('string').values
+ #return list of filenames for min(user-selected, available) images randomly selected images from the filtered dataset
+ return list(filenames)
+ else:
+ return 0
diff --git a/dashboard.py b/dashboard.py
index ca34f66..e8e8776 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -1,91 +1,68 @@
-import pandas as pd
-import plotly.express as px
from dash import Dash, html, dcc, Input, Output, State
-from components.query import get_species_options
-
-IMAGES_BASE_URL = "https://github.com/Imageomics/dashboard-prototype/blob/main/test_data/images/"
-
-df = pd.read_csv("test_data/Hoyal_Cuthill_GoldStandard_metadata_cleaned.csv")
+from components.query import get_data, get_species_options, get_images
+from components.graphs import make_hist_plot, make_map, make_pie_plot
+from components.divs import get_hist_div, get_map_div
+
+# Fixed styles and sorting options
+H1_STYLE = {'textAlign': 'center', 'color': 'MidnightBlue'}
+H4_STYLE = {'color': 'MidnightBlue', 'margin-bottom' : 10}
+HALF_DIV_STYLE = {'width': '48%', 'display': 'inline-block'}
+QUARTER_DIV_STYLE = {'width': '24%', 'display': 'inline-block'}
+SORT_LIST = [{'label': 'Alphabetical', 'value': 'alpha'},
+ {'label': 'Ascending', 'value': 'sum ascending'},
+ {'label': 'Descending', 'value': 'sum descending'}]
+
+# get dataset-determined static data:
+ # the dataframe and categorical features
+ # all possible species, subspecies
+ # distribution options (histogram and map options)
+df, cat_list = get_data()
all_species = get_species_options(df)
+hist_div = get_hist_div(cat_list, SORT_LIST, H4_STYLE, HALF_DIV_STYLE)
+map_div = get_map_div(cat_list, H4_STYLE, HALF_DIV_STYLE)
+# Initialize app/dashboard and set layout
app = Dash(__name__)
app.layout = html.Div([
- html.H1("Cuthill Data Distribution Statistics", style = {'textAlign': 'center', 'color': 'MidnightBlue'}),
+ html.H1("Cuthill Data Distribution Statistics", style = H1_STYLE),
- #Distribution Options
- html.Div([
- html.Div([
- html.H4("Show me the distribution of ...", style = {"color":"MidnightBlue", 'margin-bottom' : 10}),
- # Add dropdown options
- # x-axis (feature) distribution options: 'Subspecies', 'Additional Taxa Information', 'Locality'
- dcc.RadioItems([
- {'label': 'Subspecies', 'value': 'Subspecies'},
- {'label': 'Additional Taxa Information', 'value':'addit_taxa_info'},
- {'label': 'Locality', 'value': 'locality'}],
- 'Subspecies',
- id = 'x-variable')
- ], style = {'width': '48%', 'display': 'inline-block'}
- ),
-
- html.Div([
- html.H4("Colored by ...", style = {'color': 'MidnightBlue', 'margin-bottom' : 10}),
- #select color-by option: 'View', 'Sex', 'Hybrid Status'
- dcc.RadioItems([
- {'label':'View', 'value': 'View'},
- {'label': 'Sex', 'value': 'Sex'},
- {'label': 'Hybrid Status', 'value':'hybrid_stat'}],
- 'View',
- id = 'color-by')
- ], style = {'width': '48%', 'display': 'inline-block'}
- ),
- #html.Br(),
- html.H4("Sort distribution ", style = {'color': 'MidnightBlue', 'margin-top' : 10, 'margin-bottom' : 10}),
- dcc.RadioItems([
- {'label': 'Alphabetical', 'value': 'alpha'},
- {'label': 'Ascending', 'value': 'sum ascending'},
- {'label': 'Descending', 'value': 'sum descending'}],
- 'alpha',
- id = 'sort-by',
- inline = True),
- ], style = {'width': '48%', 'display': 'inline-block'}
+ # Distribution Options, default start on histogram
+ html.Div(hist_div,
+ id = 'dist-options',
+ style = HALF_DIV_STYLE
),
- #pie chart options
+ # Pie chart options: 'Species', 'Subspecies', 'View', 'Sex', 'Hybrid Status'
html.Div([
- html.H4("Show me the Percentage Breakdown of ...", style = {'color': 'MidnightBlue', 'margin-bottom' : 10}),
- dcc.RadioItems([
- {'label': 'Species', 'value': 'Species'},
- {'label': 'Subspecies', 'value': 'Subspecies'},
- {'label':'View', 'value': 'View'},
- {'label': 'Sex', 'value': 'Sex'},
- {'label': 'Hybrid Status', 'value':'hybrid_stat'}],
+ html.H4("Show me the Percentage Breakdown of ...", style = H4_STYLE),
+ dcc.RadioItems(cat_list[:-2],
'Species',
id = 'prct-brkdwn'
),
html.Br(),
- ], style = {'width': '48%', 'display': 'inline-block'}
+ ], style = HALF_DIV_STYLE
),
html.Br(),
html.Br(),
- #Graphs
+ # Graphs - Distribution (histogram or map), then pie chart
html.Div([
- dcc.Graph(id = 'hist-plot')], style = {'width': '48%', 'display': 'inline-block'}),
+ dcc.Graph(id = 'dist-plot')], style = HALF_DIV_STYLE),
html.Div([
- dcc.Graph(id = 'pie-plot')], style = {'width': '48%', 'display': 'inline-block'}),
+ dcc.Graph(id = 'pie-plot')], style = HALF_DIV_STYLE),
html.Hr(),
- html.H1("Cuthill Data Sample Image Selection", style = {'textAlign': 'center', 'color': 'MidnightBlue'}),
+ html.H1("Cuthill Data Sample Image Selection", style = H1_STYLE),
html.Hr(),
# Image Selector
html.Div([
- html.H4("Show me sample images of ...", style = {"color":"MidnightBlue", 'marginBottom' : 10}),
+ html.H4("Show me sample images of ...", style = H4_STYLE),
#select Species/Subspecies to view (defaul to Any)
# Note: these should be the same type to interact properly, first must not be clearable
dcc.Dropdown(options = list(all_species.keys()),
@@ -98,40 +75,40 @@
id = 'subspecies-show',
placeholder = 'Select Subspecies to View'),
# Further Refine by Features
- html.H4("that are ...", style = {"color":"MidnightBlue", 'marginBottom' : 10}),
+ html.H4("that are ...", style = H4_STYLE),
html.Div([
dcc.Checklist(df.Sex.unique(),
df.Sex.unique()[0:2],
id = 'which-sex')],
- style = {'width': '24%', 'display': 'inline-block'}
+ style = QUARTER_DIV_STYLE
),
html.Div([
dcc.Checklist(df.View.unique(),
df.View.unique()[0:2],
id = 'which-view')],
- style = {'width': '24%', 'display': 'inline-block'}
+ style = QUARTER_DIV_STYLE
),
html.Div([
dcc.Checklist(df.hybrid_stat.unique(),
df.hybrid_stat.unique()[0:2],
id = 'hybrid?')],
- style = {'width': '24%', 'display': 'inline-block'}
+ style = QUARTER_DIV_STYLE
),
html.Div([
- html.H5("How many images?", style = {"color":"MidnightBlue", 'marginBottom' : 10}),
+ html.H5("How many images?", style = H4_STYLE),
dcc.Input(type = 'number',
min = 1,
max = 100,
step = 1,
- placeholder = 1,
+ placeholder = '#',
id = 'num-images')],
- style = {'width': '24%', 'display': 'inline-block'}
+ style = QUARTER_DIV_STYLE
)
], id = 'dropdown-images'),
html.Hr(),
- #Button to activate the callback
+ # Button to activate the callback
html.Button('Display Images',
id = 'display-img',
n_clicks = 0),
@@ -140,7 +117,6 @@
html.Br(),
html.Br(),
html.Br(),
- html.Br(),
# Image Should appear
html.Div(id = 'image-1'),
@@ -153,35 +129,70 @@
])
-# Histogram Section
+# Distribution Section
+# Callback to update which options are visible (histogram vs map)
+@app.callback(
+ Output('dist-options', 'children'),
+ Input('dist-view-btn', 'n_clicks'),
+ Input('dist-view-btn', 'children')
+)
+def update_dist_view(n_clicks, children):
+ '''
+ Function to update the upper left distribution options based on selected distribution chart (histogram or map).
+ Activates on click to change, defaults to histogram view.
+
+ Parameters:
+ -----------
+ n_clicks - Number of clicks.
+ children - Label on button, determins which distribution options to show.
+
+ Returns:
+ --------
+ hist_div or map_div - The HTML Div corresponding to the selected distribution figure.
+ '''
+ if n_clicks == 0:
+ return hist_div
+ if n_clicks > 0:
+ if children == "Show Histogram":
+ return hist_div
+ else:
+ return map_div
+
+# Callback to update the distribution figure (histogram or map)
@app.callback(
- #hist output
- Output(component_id='hist-plot', component_property='figure'),
+ #dist output
+ Output(component_id='dist-plot', component_property='figure'),
#input x_var
Input(component_id='x-variable', component_property='value'),
#input color_by
Input(component_id='color-by', component_property='value'),
#input sort_by
- Input(component_id='sort-by', component_property='value')
+ Input(component_id='sort-by', component_property='value'),
+ #button information
+ Input(component_id='dist-view-btn', component_property='children')
)
-def make_hist_plot(x_var, color_by, sort_by):
- #generate histogram
- if sort_by == 'alpha':
- fig = px.histogram(df.sort_values(x_var),
- x = x_var,
- color = color_by,
- color_discrete_sequence = px.colors.qualitative.Bold)
+def update_dist_plot(x_var, color_by, sort_by, btn):
+ '''
+ Function to update distribution figure with either map or histogram based on selections.
+ Selection is based on current label of the button ('Map View' or 'Show Histogram'), which updates prior to graph.
+
+ Parameters:
+ -----------
+ x_var - User-selected variable to plot distribution.
+ color_by - User-selected property to color the plot by.
+ sort_by - User-selected ordering of bar charts (Alphabetical, Ascending, or Descending).
+ btn - Current label of the button ('Map View' or 'Show Histogram').
+
+ Returns:
+ --------
+ fig - Figure returned from appropriate function call: histogram or map of the distribution of the requested variable.
+ '''
+ if btn == "Show Histogram":
+ return make_map(df, color_by)
else:
- fig = px.histogram(df,
- x = x_var,
- color = color_by,
- color_discrete_sequence = px.colors.qualitative.Bold).update_xaxes(categoryorder = sort_by)
-
- fig.update_layout(title = {'text': f'Distribution of {x_var} Colored by {color_by}'})
-
- return fig
+ return make_hist_plot(df, x_var, color_by, sort_by)
# Pie Section
@@ -192,22 +203,19 @@ def make_hist_plot(x_var, color_by, sort_by):
Input(component_id='prct-brkdwn', component_property='value')
)
-def make_pie_plot(var):
- #generate pie chart
- if(var == 'Subspecies'):
- pie_fig = px.pie(df,
- names = var,
- color_discrete_sequence = px.colors.qualitative.Bold,
- hover_data = ['Species'])
- else:
- pie_fig = px.pie(df,
- names = var,
- color_discrete_sequence = px.colors.qualitative.Bold)
- pie_fig.update_traces(textposition = 'inside', textinfo = 'percent+label')
-
- pie_fig.update_layout(title = {'text': f'Percentage Breakdown of {var}'})
+def update_pie_plot(var):
+ '''
+ Updates the pie chart of dataset specimens based on user selection of variable to color by.
- return pie_fig
+ Parameters:
+ -----------
+ var - User-selected categorical variable by which to color.
+
+ Returns:
+ --------
+ fig - Pie chart figure returned from function call: percentage breakdown of `var` samples in the dataset.
+ '''
+ return make_pie_plot(df, var)
# Image Section
@@ -218,7 +226,7 @@ def make_pie_plot(var):
)
def set_subspecies_options(selected_species):
- # Set subspecies options based on selected species
+ # Set subspecies options in dropdown based on user-selected species.
return [{'label': i, 'value': i} for i in all_species[selected_species]]
# Callback for Image Subspecies Selection
@@ -228,7 +236,7 @@ def set_subspecies_options(selected_species):
)
def set_subspecies_value(available_options):
- # Collect selected subspecies
+ # Collect selected subspecies to display in multi-select dropdown.
return available_options[0]['value']
# Image & Display Images Button Callback
@@ -244,60 +252,31 @@ def set_subspecies_value(available_options):
prevent_initial_call = True
)
+# Retrieve selected number of images
def update_display(n_clicks, subspecies, view, sex, hybrid, num_images):
- if n_clicks > 0:
- return get_images(subspecies, view, sex, hybrid, num_images)
- else:
- return html.H4("Please make a selection.",
- style = {"color":"MidnightBlue"})
+ '''
+ Function to retrieve the user-selected number of images adhering to their chosen parameters when the 'Display Images' button is pressed.
-# Retrieve selected number of images
-def get_images(subspecies, view, sex, hybrid, num_images):
- #if n_clicks is None:
- # raise PreventUpdate
- # else:
- filenames = get_filenames(subspecies, view, sex, hybrid, num_images)
- if filenames == 0:
- #print("No Such Images. Please make another selection.")
- return html.H4("No Such Images. Please make another selection.",
- style = {"color":"MidnightBlue"})
- Imgs = []
- for filename in filenames:
- if 'D_lowres' in filename:
- image_directory = "dorsal_images/"
- else:
- image_directory = "ventral_images/"
- #remove 'tif' from filename and replace with 'png' in url
- image_path = IMAGES_BASE_URL + image_directory + filename[:-3] + "png?raw=true"
- Imgs.append(html.Img(src = image_path))
- return Imgs
-
-def get_filenames(subspecies, view, sex, hybrid, num_images):
- #filter df by subspecies, then view, sex and hybrid
- #return filenames for num_images randomly selected images from the filtered dataset
- #default to 1 if none selected
- #check for Any-Melpomene, Any-Erato, or Any (general)
- if 'Any' in subspecies:
- if subspecies == 'Any':
- df_sub = df.copy()
- else:
- subspecies = subspecies.split('-')[1].lower()
- df_sub = df.loc[df.Subspecies == subspecies].copy()
- else:
- df_sub = df.loc[df.Subspecies.isin(subspecies)].copy()
- df_sub = df_sub.loc[df_sub.View.isin(view)]
- df_sub = df_sub.loc[df_sub.Sex.isin(sex)]
- df_filtered = df_sub.loc[df_sub.hybrid_stat.isin(hybrid)]
- max_imgs = len(df_filtered)
- if max_imgs > 0:
- if num_images == None:
- num = 1
- else:
- num = min(num_images, max_imgs)
- filenames = df_filtered.sample(num).Image_filename.astype('string').values
- return list(filenames)
+ Parameters:
+ -----------
+ n_clicks - Number of times the 'Display Images' button has been pressed.
+ subspecies - String. Subspecies of specimen selected by the user.
+ view - String. View of specimen selected by the user.
+ sex - String. Sex of specimen selected by the user.
+ hybrid - String. Hybrid status of specimen selected by the user.
+ num_images - Integer. Number of images requested by the user. Default value is 1 (in get_filename).
+
+ Returns:
+ --------
+ Imgs - (Return of function call) List of html image elements with `src` element pointing to paths for the requested number of images matching given parameters.
+ Returns html header4 "No Such Images. Please make another selection." if no images matching parameters exist.
+ Returns html header4 "Please make a selection." If number of images isn't specified.
+ '''
+ if n_clicks > 0 and (view != [] and sex != [] and hybrid != []):
+ return get_images(df, subspecies, view, sex, hybrid, num_images)
else:
- return 0
+ return html.H4("Please make a selection.",
+ style = {'color': 'MidnightBlue'})
if __name__ == '__main__':
- app.run_server(debug=True)
\ No newline at end of file
+ app.run_server(debug=True)
diff --git a/dashboard_preview.png b/dashboard_preview.png
deleted file mode 100644
index 9da0d20..0000000
Binary files a/dashboard_preview.png and /dev/null differ
diff --git a/dashboard_preview_hist.png b/dashboard_preview_hist.png
new file mode 100644
index 0000000..38f2fd7
Binary files /dev/null and b/dashboard_preview_hist.png differ
diff --git a/dashboard_preview_map.png b/dashboard_preview_map.png
new file mode 100644
index 0000000..dae491f
Binary files /dev/null and b/dashboard_preview_map.png differ