-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Filter app #104
Filter app #104
Changes from 15 commits
c783a28
21d08a9
dcffdc4
77efbfe
df86989
f310a6f
2fc470e
58b5e99
2aac567
6007748
afd61f7
d34fc11
f94c881
96a4d91
8000a25
d89b497
d6afa2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,6 @@ recursive=y | |
|
||
fail-under=9.7 | ||
|
||
jobs=0 | ||
|
||
max-line-length=120 | ||
|
||
suggestion-mode=yes | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,118 +1,49 @@ | ||
""" | ||
Main app file for ELUC demo. | ||
Uses many 'components' to separate divs and their related callbacks. | ||
They aren't necessarily truly reusable components, but they help to organize the code. | ||
Main entrypoint to run the app. Contains the layout of the app and registers all the callbacks of each component. | ||
""" | ||
import pandas as pd | ||
from dash import Dash | ||
from dash import dcc | ||
from dash import html | ||
from dash import Dash, html | ||
import dash_bootstrap_components as dbc | ||
import pandas as pd | ||
|
||
import app.constants as app_constants | ||
from app.components.chart import ChartComponent | ||
from app.components.legend import LegendComponent | ||
from app.components.lock import LockComponent | ||
from app.components.map import MapComponent | ||
from app.components.prediction import PredictionComponent | ||
from app.components.prescription import PrescriptionComponent | ||
from app.components.intro import IntroComponent | ||
from app.components.context.context import ContextComponent | ||
from app.components.filter import FilterComponent | ||
from app.components.dms.dms import DMSComponent | ||
from app.components.references import ReferencesComponent | ||
from app.components.sliders import SlidersComponent | ||
from app.components.trivia import TriviaComponent | ||
from app.utils import EvolutionHandler | ||
|
||
|
||
app = Dash(__name__, | ||
external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.BOOTSTRAP], | ||
prevent_initial_callbacks="initial_duplicate") | ||
server = app.server | ||
|
||
df = pd.read_csv(app_constants.DATA_FILE_PATH, index_col=app_constants.INDEX_COLS) | ||
|
||
legend_component = LegendComponent() | ||
|
||
map_component = MapComponent(df) | ||
map_component.register_update_map_callback(app) | ||
map_component.register_click_map_callback(app) | ||
map_component.register_select_country_callback(app) | ||
|
||
prescription_component = PrescriptionComponent(df) | ||
prescription_component.register_select_prescriptor_callback(app) | ||
prescription_component.register_toggle_modal_callback(app) | ||
|
||
sliders_component = SlidersComponent(df) | ||
sliders_component.register_set_frozen_reset_sliders_callback(app) | ||
sliders_component.register_show_slider_value_callback(app) | ||
sliders_component.register_sum_to_one_callback(app) | ||
|
||
lock_component = LockComponent() | ||
|
||
chart_component = ChartComponent(df) | ||
chart_component.register_update_context_chart_callback(app) | ||
chart_component.register_update_presc_chart_callback(app) | ||
app.title = 'Land Use Optimization' | ||
|
||
prediction_component = PredictionComponent(df) | ||
prediction_component.register_predictor_callback(app) | ||
prediction_component.register_land_use_callback(app) | ||
app_df = pd.read_csv("app/data/app_data.csv", index_col=app_constants.INDEX_COLS) | ||
|
||
trivia_component = TriviaComponent(df) | ||
trivia_component.register_update_trivia_callback(app) | ||
evolution_handler = EvolutionHandler() | ||
|
||
intro_component = IntroComponent() | ||
context_component = ContextComponent(app_df, evolution_handler) | ||
filter_component = FilterComponent(evolution_handler) | ||
dms_component = DMSComponent(app_df, evolution_handler) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We pass our app df and evolution handler to the components so that they have the ability to prescribe, look at context, etc. |
||
references_component = ReferencesComponent() | ||
|
||
app.title = 'Land Use Optimization' | ||
app.css.config.serve_locally = False | ||
# Don't be afraid of the 3rd party URLs: chriddyp is the author of Dash! | ||
# These two allow us to dim the screen while loading. | ||
# See discussion with Dash devs here: https://community.plotly.com/t/dash-loading-states/5687 | ||
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'}) | ||
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/brPBPO.css'}) | ||
|
||
app.layout = html.Div([ | ||
dcc.Markdown(''' | ||
# Land Use Optimization | ||
This site is for demonstration purposes only. | ||
|
||
For a given context cell representing a portion of the earth, | ||
identified by its latitude and longitude coordinates, and a given year: | ||
* What changes can we make to the land usage | ||
* In order to minimize the resulting estimated CO2 emissions? (Emissions from Land Use Change, ELUC, | ||
in tons of carbon per hectare) | ||
context_component.register_callbacks(app) | ||
filter_component.register_callbacks(app) | ||
dms_component.register_callbacks(app) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of all the long series of callbacks we now register all the callbacks at once with these simple functions. We could abstract these into a general |
||
|
||
'''), | ||
dcc.Markdown('''## Context'''), | ||
html.Div([ | ||
dcc.Graph(id="map", figure=map_component.get_map_fig(), style={"grid-column": "1"}), | ||
html.Div([map_component.get_context_div()], style={"grid-column": "2"}), | ||
html.Div([legend_component.get_legend_div()], style={"grid-column": "3"}) | ||
], style={"display": "grid", "grid-template-columns": "auto 1fr auto", 'position': 'relative'}), | ||
dcc.Markdown('''## Actions'''), | ||
html.Div([ | ||
html.Div([prescription_component.get_presc_select_div()], style={"grid-column": "1"}), | ||
html.Div([chart_component.get_chart_select_div()], | ||
style={"grid-column": "2", "margin-top": "-10px", "margin-left": "10px"}), | ||
], style={"display": "grid", "grid-template-columns": "45% 15%"}), | ||
html.Div([ | ||
html.Div(lock_component.get_checklist_div(), style={"grid-column": "1", "height": "100%"}), | ||
html.Div(sliders_component.get_sliders_div(), style={'grid-column': '2'}), | ||
dcc.Graph(id='context-fig', | ||
figure=chart_component.create_treemap(type_context=True), | ||
style={'grid-column': '3'}), | ||
dcc.Graph(id='presc-fig', | ||
figure=chart_component.create_treemap(type_context=False), | ||
style={'grid-clumn': '4'}) | ||
], style={'display': 'grid', 'grid-template-columns': '4.5% 40% 1fr 1fr', "width": "100%"}), | ||
# The above line can't be set to auto because the lines will overflow! | ||
html.Div([ | ||
sliders_component.get_frozen_div(), | ||
html.Button("Sum to 100%", id='sum-button', n_clicks=0), | ||
html.Div(id='sum-warning') | ||
]), | ||
dcc.Markdown('''## Outcomes'''), | ||
prediction_component.get_predict_div(), | ||
dcc.Markdown('''## Trivia'''), | ||
trivia_component.get_trivia_div(), | ||
dcc.Markdown('''## References'''), | ||
references_component.get_references_div() | ||
], style={'padding-left': '10px'},) | ||
app.layout = html.Div( | ||
children=[ | ||
intro_component.get_div(), | ||
context_component.get_div(), | ||
filter_component.get_div(), | ||
dms_component.get_div(), | ||
references_component.get_references_div() | ||
] | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Layout is now super clean. Just calls get_div from each of our components in the order they should display on the apge |
||
|
||
if __name__ == '__main__': | ||
app.run_server(host='0.0.0.0', debug=False, port=4057, use_reloader=False, threaded=False) | ||
app.run_server(host='0.0.0.0', debug=False, port=4057, use_reloader=True, threaded=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean to leave user_reloader to True? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to https://dash.plotly.com/devtools you're not changing the code in the deployed app, and it makes it slower to start, so I'll leave it to False |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/* body { | ||
background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/Icelandic_Landscape_near_Neskaupsta%C3%B0ur_July_2014.JPG/640px-Icelandic_Landscape_near_Neskaupsta%C3%B0ur_July_2014.JPG"); | ||
background-size: cover; | ||
background-position: center; | ||
background-attachment: fixed; | ||
} */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
window.dccFunctions = window.dccFunctions || {}; | ||
window.dccFunctions.percentSlider = function(value) { | ||
return Math.round(value * 100); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So that our sliders show as percents and not proportions (10% vs. 0.1) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
""" | ||
Context component class for selecting context. | ||
""" | ||
from dash import html, dcc, Output, Input | ||
import dash_bootstrap_components as dbc | ||
import pandas as pd | ||
import regionmask | ||
|
||
from app.components.context.map import MapComponent | ||
from app.utils import EvolutionHandler | ||
from data import constants | ||
|
||
|
||
class ContextComponent(): | ||
""" | ||
Component containing map as well as dropdowns and input fields for picking a more specific context. | ||
""" | ||
def __init__(self, app_df: pd.DataFrame, handler: EvolutionHandler): | ||
self.map_component = MapComponent(app_df) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We reuse the old map component in our new context component |
||
self.app_df = app_df | ||
self.handler = handler | ||
self.countries_df = regionmask.defined_regions.natural_earth_v5_0_0.countries_110.to_dataframe() | ||
|
||
def create_label_and_value(self, label: str, value: html.Div) -> html.Div: | ||
""" | ||
Standard dash function that pairs a label with any arbitrary value Div. | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function lines up a label with a div so we reuse it for the Lat/dropdown, lon/dropdown, year/input |
||
div = html.Div( | ||
className="d-flex flex-row", | ||
children=[ | ||
html.Label(label, className="w-25"), | ||
html.Div( | ||
value, | ||
className="flex-grow-1" | ||
) | ||
] | ||
) | ||
return div | ||
|
||
def get_div(self): | ||
""" | ||
Returns the entire context div to display in the app. | ||
""" | ||
div = html.Div( | ||
className="mb-5 mx-5", | ||
children=[ | ||
dbc.Row( | ||
children=[ | ||
dbc.Col( | ||
width={"offset": 3, "size": 3}, | ||
children=[ | ||
dcc.Graph(id="map", figure=self.map_component.get_map_fig()), | ||
dcc.Dropdown( | ||
id="loc-dropdown", | ||
options=list(self.map_component.countries_df["names"]), | ||
value=list(self.map_component.countries_df["names"])[143] | ||
) | ||
] | ||
), | ||
dbc.Col( | ||
width=3, | ||
children=[ | ||
html.B("1. Select a land area on the map to optimize or manually enter coordinates."), | ||
self.create_label_and_value( | ||
"Latitude", | ||
dcc.Dropdown( | ||
id="lat-dropdown", | ||
options=[{"label": lat, | ||
"value": lat} for lat in self.map_component.lat_list], | ||
value=51.625, | ||
) | ||
), | ||
self.create_label_and_value( | ||
"Longitude", | ||
dcc.Dropdown( | ||
id="lon-dropdown", | ||
options=[{"label": lon, | ||
"value": lon} for lon in self.map_component.lon_list], | ||
value=-3.375, | ||
) | ||
), | ||
self.create_label_and_value( | ||
"Year", | ||
html.Div([ | ||
dcc.Input( | ||
id="year-input", | ||
type="number", | ||
value=2021, | ||
debounce=True | ||
), | ||
dcc.Tooltip(f"Year must be between \ | ||
{self.map_component.min_time} and \ | ||
{self.map_component.max_time}.") | ||
]) | ||
) | ||
] | ||
) | ||
] | ||
) | ||
] | ||
) | ||
return div | ||
|
||
def register_callbacks(self, app): | ||
""" | ||
Registers callbacks to make app interactive. Registers old map callbacks as well as new one to run prescription. | ||
""" | ||
self.map_component.register_click_map_callback(app) | ||
self.map_component.register_select_country_callback(app) | ||
self.map_component.register_update_map_callback(app) | ||
|
||
@app.callback( | ||
Output("results-store", "data"), | ||
Input("year-input", "value"), | ||
Input("lat-dropdown", "value"), | ||
Input("lon-dropdown", "value") | ||
) | ||
def run_prescription(year: int, lat: float, lon: float) -> dict[str: list]: | ||
""" | ||
Runs prescription for the selected context on all prescriptors. Returns the results as a json to a store. | ||
""" | ||
condition = (self.app_df["time"] == year) & (self.app_df["lat"] == lat) & (self.app_df["lon"] == lon) | ||
context_df = self.app_df[condition] | ||
context_df = context_df[constants.CAO_MAPPING["context"]].iloc[0:1] | ||
results_df = self.handler.prescribe_all(context_df) | ||
results_json = results_df.to_dict(orient="records") | ||
return results_json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We get rid of all the old components but reuse some in our new components. The components now correspond to portions of the page rather than by functionality which makes things neater