Spaces:
Running
Running
| from math import isclose | |
| import os | |
| import numpy as np | |
| import pandas as pd | |
| import regionmask | |
| import plotly.graph_objects as go | |
| from dash import ALL | |
| from dash import MATCH | |
| from dash import Dash | |
| from dash import Input | |
| from dash import Output | |
| from dash import State | |
| from dash import dcc | |
| from dash import html | |
| import dash_bootstrap_components as dbc | |
| from . import Predictor | |
| from . import Prescriptor | |
| from . import constants | |
| from . import utils | |
| app = Dash(__name__, | |
| external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.BOOTSTRAP], | |
| prevent_initial_callbacks="initial_duplicate") | |
| server = app.server | |
| df = pd.read_csv(constants.DATA_FILE_PATH, index_col=constants.INDEX_COLS) | |
| countries_df = regionmask.defined_regions.natural_earth_v5_0_0.countries_110.to_dataframe() | |
| # Prescriptor list should be in order of least to most change | |
| pareto_df = pd.read_csv(constants.PARETO_CSV_PATH) | |
| prescriptor_list = list(pareto_df["id"]) | |
| # Cells | |
| min_lat = df.index.get_level_values("lat").min() | |
| max_lat = df.index.get_level_values("lat").max() | |
| min_lon = df.index.get_level_values("lon").min() | |
| max_lon = df.index.get_level_values("lon").max() | |
| min_time = df.index.get_level_values("time").min() | |
| max_time = df.index.get_level_values("time").max() | |
| lat_list = list(np.arange(min_lat, max_lat + constants.GRID_STEP, constants.GRID_STEP)) | |
| lon_list = list(np.arange(min_lon, max_lon + constants.GRID_STEP, constants.GRID_STEP)) | |
| map_fig = go.Figure() | |
| # Load predictors | |
| predictors = utils.load_predictors() | |
| # Legend examples come from https://hess.copernicus.org/preprints/hess-2021-247/hess-2021-247-ATC3.pdf | |
| legend_div = html.Div( | |
| style={}, | |
| children = [ | |
| dcc.Markdown(''' | |
| ### Land Use Types | |
| Primary: Vegetation that is untouched by humans | |
| - primf: Primary forest | |
| - primn: Primary nonforest vegetation | |
| Secondary: Vegetation that has been touched by humans | |
| - secdf: Secondary forest | |
| - secdn: Secondary nonforest vegetation | |
| Urban | |
| Crop | |
| - c3ann: Annual C3 crops (e.g. wheat) | |
| - c4ann: Annual C4 crops (e.g. maize) | |
| - c3per: Perennial C3 crops (e.g. banana) | |
| - c4per: Perennial C4 crops (e.g. sugarcane) | |
| - c3nfx: Nitrogen fixing C3 crops (e.g. soybean) | |
| Pasture | |
| - pastr: Managed pasture land | |
| - range: Natural grassland/savannah/desert/etc. | |
| ''') | |
| ] | |
| ) | |
| context_div = html.Div( | |
| style={'display': 'grid', | |
| 'grid-template-columns': 'auto 1fr', 'grid-template-rows': 'auto auto auto auto', | |
| 'position': 'absolute', 'bottom': '0'}, | |
| children=[ | |
| html.P("Region", style={'grid-column': '1', 'grid-row': '1', 'padding-right': '10px'}), | |
| dcc.Dropdown( | |
| id="loc-dropdown", | |
| options=list(countries_df["names"]), | |
| value=list(countries_df["names"])[143], | |
| style={'grid-column': '2', 'grid-row': '1', 'width': '75%', 'justify-self': 'left', 'margin-top': '-3px'} | |
| ), | |
| html.P("Lat", style={'grid-column': '1', 'grid-row': '2', 'padding-right': '10px'}), | |
| dcc.Dropdown( | |
| id='lat-dropdown', | |
| options=lat_list, | |
| placeholder="Select a latitude", | |
| value=51.625, | |
| style={'grid-column': '2', 'grid-row': '2', 'width': '75%', 'justify-self': 'left', 'margin-top': '-3px',} | |
| ), | |
| html.P("Lon", style={'grid-column': '1', 'grid-row': '3', 'padding-right': '10px'}), | |
| dcc.Dropdown( | |
| id='lon-dropdown', | |
| options=lon_list, | |
| placeholder="Select a longitude", | |
| value=-3.375, | |
| style={'grid-column': '2', 'grid-row': '3', 'width': '75%', 'justify-self': 'left', 'margin-top': '-3px'} | |
| ), | |
| html.P("Year ", style={'grid-column': '1', 'grid-row': '4', 'margin-right': '10px'}), | |
| html.Div([ | |
| dcc.Input( | |
| id="year-input", | |
| type="number", | |
| value=2021, | |
| debounce=True | |
| ), | |
| dcc.Tooltip(f"Year must be between {min_time} and {max_time}."), | |
| ], style={'grid-column': '2', 'grid-row': '4', 'width': '75%', 'justify-self': 'left', 'margin-top': '-3px'}), | |
| ] | |
| ) | |
| presc_select_div = html.Div([ | |
| html.P("Minimize change", style={"grid-column": "1"}), | |
| html.Div([ | |
| dcc.Slider(id='presc-select', | |
| min=0, max=len(prescriptor_list)-1, step=1, | |
| value=constants.DEFAULT_PRESCRIPTOR_IDX, | |
| included=False, | |
| marks={i : "" for i in range(len(prescriptor_list))}) | |
| ], style={"grid-column": "2", "width": "100%", "margin-top": "8px"}), | |
| html.P("Minimize ELUC", style={"grid-column": "3", "padding-right": "10px"}), | |
| html.Button("Prescribe", id='presc-button', n_clicks=0, style={"grid-column": "4", "margin-top": "-10px"}), | |
| html.Button("View Pareto", id='pareto-button', n_clicks=0, style={"grid-column": "5", "margin-top": "-10px"}), | |
| dbc.Modal( | |
| [ | |
| dbc.ModalHeader("Pareto front"), | |
| dcc.Graph(id='pareto-fig', figure=utils.create_pareto(pareto_df=pareto_df, | |
| presc_id=prescriptor_list[constants.DEFAULT_PRESCRIPTOR_IDX])), | |
| ], | |
| id="pareto-modal", | |
| is_open=False, | |
| ), | |
| ], style={"display": "grid", "grid-template-columns": "auto 1fr auto auto", "width": "100%", "align-content": "center"}) | |
| chart_select_div = dcc.Dropdown( | |
| options=constants.CHART_TYPES, | |
| id="chart-select", | |
| value=constants.CHART_TYPES[0], | |
| clearable=False | |
| ) | |
| check_options = utils.create_check_options(constants.RECO_COLS) | |
| checklist_div = html.Div([ | |
| dcc.Checklist(check_options, id="locks", inputStyle={"margin-bottom": "30px"}) | |
| ]) | |
| sliders_div = html.Div([ | |
| html.Div([ | |
| #html.P(col, style={"grid-column": "1"}), | |
| html.Div([ | |
| dcc.Slider( | |
| min=0, | |
| max=1, | |
| step=constants.SLIDER_PRECISION, | |
| value=0, | |
| marks=None, | |
| tooltip={"placement": "bottom", "always_visible": False}, | |
| id={"type": "presc-slider", "index": f"{col}"} | |
| ) | |
| ], style={"grid-column": "1", "width": "100%", "margin-top": "8px"}), | |
| dcc.Input( | |
| value="0%", | |
| type="text", | |
| disabled=True, | |
| id={"type": "slider-value", "index": f"{col}"}, | |
| style={"grid-column": "2", "text-align": "right", "margin-top": "-5px"}), | |
| ], style={"display": "grid", "grid-template-columns": "1fr 15%"}) for col in constants.RECO_COLS] | |
| ) | |
| frozen_div = html.Div([ | |
| dcc.Input( | |
| value=f"{col}: 0.00%", | |
| type="text", | |
| disabled=True, | |
| id={"type": "frozen-input", "index": f"{col}-frozen"}) for col in constants.NO_CHANGE_COLS + ["nonland"] | |
| ]) | |
| predict_div = html.Div([ | |
| dcc.Dropdown(list((predictors.keys())), list(predictors.keys())[0], id="pred-select", style={"width": "200px"}), | |
| html.Button("Predict", id='predict-button', n_clicks=0,), | |
| html.Label("Predicted ELUC:", style={'padding-left': '10px'}), | |
| dcc.Input( | |
| value="", | |
| type="text", | |
| disabled=True, | |
| id="predict-eluc", | |
| ), | |
| html.Label("tC/ha", style={'padding-left': '2px'}), | |
| html.Label("Land Change:", style={'padding-left': '10px'}), | |
| dcc.Input( | |
| value="", | |
| type="text", | |
| disabled=True, | |
| id="predict-change", | |
| ), | |
| html.Label("%", style={'padding-left': '2px'}), | |
| ], style={"display": "flex", "flex-direction": "row", "width": "90%", "align-items": "center"}) | |
| inline_block = {"display": "inline-block", "padding-right": "10px"} | |
| trivia_div = html.Div([ | |
| html.Div(className="parent", children=[ | |
| html.P("Total emissions reduced from this land use change: ", className="child", style=inline_block), | |
| html.P(id="total-em", style={"font-weight": "bold"}|inline_block) | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.I(className="bi bi-airplane", style=inline_block), | |
| html.P("Flight emissions from flying JFK to Geneva: ", className="child", style=inline_block), | |
| html.P(f"{constants.CO2_JFK_GVA} tonnes CO2", style={"font-weight": "bold"}|inline_block) | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.I(className="bi bi-airplane", style=inline_block), | |
| html.P("Plane tickets mitigated: ", className="child", style=inline_block), | |
| html.P(id="tickets", style={"font-weight": "bold"}|inline_block) | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.I(className="bi bi-person", style=inline_block), | |
| html.P("Total yearly carbon emissions of average world citizen: ", className="child", style=inline_block), | |
| html.P(f"{constants.CO2_PERSON} tonnes CO2", style={"font-weight": "bold"}|inline_block) | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.I(className="bi bi-person", style=inline_block), | |
| html.P("Number of peoples' carbon emissions mitigated from this change : ", className="child", style=inline_block), | |
| html.P(id="people", style={"font-weight": "bold"}|inline_block) | |
| ]), | |
| html.P("(Sources: https://flightfree.org/flight-emissions-calculator https://scied.ucar.edu/learning-zone/climate-solutions/carbon-footprint)", style={"font-size": "10px"}) | |
| ]) | |
| references_div = html.Div([ | |
| html.Div(className="parent", children=[ | |
| html.P("Code for this project can be found here: ", | |
| className="child", style=inline_block), | |
| html.A("(Project Resilience MVP repo)", href="https://github.com/Project-Resilience/mvp/tree/main/use_cases/eluc\n"), | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.P("The paper for this project can be found here: ", | |
| className="child", style=inline_block), | |
| html.A("(arXiv link)", href="https://arxiv.org/abs/2311.12304\n"), | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.P("ELUC data provided by the BLUE model ", | |
| className="child", style=inline_block), | |
| html.A("(BLUE: Bookkeeping of land use emissions)", href="https://agupubs.onlinelibrary.wiley.com/doi/10.1002/2014GB004997\n"), | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.P("Land use change data provided by the LUH2 project", | |
| className="child", style=inline_block), | |
| html.A("(LUH2: Land Use Harmonization 2)", href="https://luh.umd.edu/\n"), | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.P("Setup is described in Appendix C2.1 of the GCB 2022 report", | |
| className="child", style=inline_block), | |
| html.A("(Global Carbon Budget 2022 report)", href="https://essd.copernicus.org/articles/14/4811/2022/#section10/\n"), | |
| ]), | |
| html.Div(className="parent", children=[ | |
| html.P("The Global Carbon Budget report assesses the global CO2 budget for the Intergovernmental Panel on Climate Change", | |
| className="child", style=inline_block), | |
| html.A("(IPCC)", href="https://www.ipcc.ch/\n"), | |
| ]), | |
| ]) | |
| def toggle_modal(n, is_open, presc_idx): | |
| """ | |
| Toggles pareto modal. | |
| :param n: Number of times button has been clicked. | |
| :param is_open: Whether the modal is open. | |
| :param presc_idx: The index of the prescriptor to show. | |
| :return: The new state of the modal and the figure to show. | |
| """ | |
| fig = utils.create_pareto(pareto_df, prescriptor_list[presc_idx]) | |
| if n: | |
| return not is_open, fig | |
| return is_open, fig | |
| def click_map(click_data): | |
| """ | |
| Selects context when point on map is clicked. | |
| :param click_data: Input data from click action. | |
| :return: The new longitude and latitude to put into the dropdowns. | |
| """ | |
| return click_data["points"][0]["lat"], click_data["points"][0]["lon"] | |
| def select_country(location, year): | |
| """ | |
| Changes the selected country and relocates map to a valid lat/lon. | |
| This makes the update_map function only load the current country's data. | |
| :param location: Selected country name. | |
| :param year: Used to get proper # of points to sample from. | |
| :return: A sample latitude/longitude point within the selected country. | |
| """ | |
| country_idx = countries_df[countries_df["names"] == location].index[0] | |
| samples = df[df["country"] == country_idx].loc[year] | |
| example = samples.iloc[len(samples) // 2] | |
| return example.name[0], example.name[1] | |
| def update_map(year, lat, lon, location): | |
| """ | |
| Updates map data behind the scenes when year is clicked. | |
| Changes focus when region is selected. | |
| :param location: Selected country name. | |
| :param year: The selected year. | |
| :return: A newly created map. | |
| """ | |
| country_idx = countries_df[countries_df["names"] == location].index[0] | |
| # Filter data by year and location | |
| data = df.loc[year] | |
| data = data[data["country"] == country_idx] | |
| data = data.copy().reset_index() | |
| # Find colored point | |
| lat_lon = (data["lat"] == lat) & (data["lon"] == lon) | |
| idx = data[lat_lon].index[0] | |
| return utils.create_map(data, 10, idx) | |
| def set_frozen_reset_sliders(lat, lon, year): | |
| """ | |
| Resets prescription sliders to 0 to avoid confusion. | |
| Also sets prescription sliders' max values to 1 - no change cols to avoid negative values. | |
| :param lat: Selected latitude. | |
| :param lon: Selected longitude. | |
| :param year: Selected year. | |
| :return: Frozen values, slider values, and slider max. | |
| """ | |
| context = df.loc[year, lat, lon] | |
| chart_data = utils.add_nonland(context[constants.LAND_USE_COLS]) | |
| frozen_cols = constants.NO_CHANGE_COLS + ["nonland"] | |
| frozen = chart_data[frozen_cols].tolist() | |
| frozen = [f"{frozen_cols[i]}: {frozen[i]*100:.2f}%" for i in range(len(frozen_cols))] | |
| reset = [0 for _ in constants.RECO_COLS] | |
| max_val = chart_data[constants.RECO_COLS].sum() | |
| maxes = [max_val for _ in range(len(constants.RECO_COLS))] | |
| return frozen, reset, maxes | |
| def update_context_chart(chart_type, year, lat, lon): | |
| """ | |
| Updates context chart when context store is updated or chart type is changed. | |
| :param chart_type: String input from chart select dropdown. | |
| :param year: Selected context year. | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :return: New figure type selected by chart_type with data context. | |
| """ | |
| context = df.loc[year, lat, lon] | |
| chart_data = utils.add_nonland(context[constants.LAND_USE_COLS]) | |
| assert chart_type in ("Treemap", "Pie Chart") | |
| if chart_type == "Treemap": | |
| return utils.create_treemap(chart_data, type_context=True, year=year) | |
| return utils.create_pie(chart_data, type_context=True, year=year) | |
| def select_prescriptor(n_clicks, presc_idx, year, lat, lon): | |
| """ | |
| Selects prescriptor, runs on context, updates sliders. | |
| :param n_clicks: Unused number of times button has been clicked. | |
| :param presc_idx: Index of prescriptor in PRESCRIPTOR_LIST to load. | |
| :param year: Selected context year. | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :return: Updated slider values. | |
| """ | |
| presc_id = prescriptor_list[presc_idx] | |
| prescriptor = Prescriptor.Prescriptor(presc_id) | |
| context = df.loc[year, lat, lon][constants.CONTEXT_COLUMNS] | |
| context_df = pd.DataFrame([context]) | |
| prescribed = prescriptor.run_prescriptor(context_df) | |
| return prescribed.iloc[0].tolist() | |
| def show_slider_value(slider): | |
| """ | |
| Displays slider values next to sliders. | |
| :param sliders: Slider values. | |
| :return: Slider values. | |
| """ | |
| return f"{slider * 100:.2f}%" | |
| def compute_land_change(sliders, year, lat, lon, locked): | |
| """ | |
| Computes land change percent for output. | |
| Warns user if values don't sum to 1. | |
| :param sliders: Slider values to store. | |
| :param year: Selected context year. | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :param locked: Locked columns to check for warning. | |
| :return: Warning if necessary, land change percent. | |
| """ | |
| context = df.loc[year, lat, lon][constants.LAND_USE_COLS] | |
| presc = pd.Series(sliders, index=constants.RECO_COLS) | |
| warnings = [] | |
| # Check if prescriptions sum to 1 | |
| # TODO: Are we being precise enough? | |
| new_sum = presc.sum() | |
| old_sum = context[constants.RECO_COLS].sum() | |
| if not isclose(new_sum, old_sum, rel_tol=1e-7): | |
| warnings.append(html.P(f"WARNING: Please make sure prescriptions sum to: {str(old_sum * 100)} instead of {str(new_sum * 100)} by clicking \"Sum to 100\"")) | |
| # Check if sum of locked prescriptions are > sum(land use) | |
| # TODO: take a look at this logic. | |
| if locked and presc[locked].sum() > old_sum: | |
| warnings.append(html.P("WARNING: Sum of locked prescriptions is greater than sum of land use. Please reduce one before proceeding")) | |
| # Check if any prescriptions below 0 | |
| if (presc < 0).any(): | |
| warnings.append(html.P("WARNING: Negative values detected. Please lower the value of a locked slider.")) | |
| # Compute total change | |
| change = utils.compute_percent_change(context, presc) | |
| return warnings, f"{change * 100:.2f}" | |
| def update_presc_chart(chart_type, sliders, year, lat, lon): | |
| """ | |
| Updates prescription pie from store according to chart type. | |
| :param chart_type: String input from chart select dropdown. | |
| :param sliders: Prescribed slider values. | |
| :param year: Selected context year (also for title of chart). | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :return: New chart of type chart_type using presc data. | |
| """ | |
| # If we have no prescription just return an empty chart | |
| if all(slider == 0 for slider in sliders): | |
| return utils.create_treemap(pd.Series([]), type_context=False, year=year) | |
| presc = pd.Series(sliders, index=constants.RECO_COLS) | |
| context = df.loc[year, lat, lon] | |
| chart_data = context[constants.LAND_USE_COLS].copy() | |
| chart_data[constants.RECO_COLS] = presc[constants.RECO_COLS] | |
| # Manually calculate nonland from context so that it's not zeroed out by sliders. | |
| nonland = 1 - context[constants.LAND_USE_COLS].sum() | |
| nonland = nonland if nonland > 0 else 0 | |
| chart_data["nonland"] = nonland | |
| assert chart_type in ("Treemap", "Pie Chart") | |
| if chart_type == "Treemap": | |
| return utils.create_treemap(chart_data, type_context=False, year=year) | |
| return utils.create_pie(chart_data, type_context=False, year=year) | |
| def sum_to_1(n_clicks, sliders, year, lat, lon, locked): | |
| """ | |
| Sets slider values to sum to how much land was used in context. | |
| Subtracts locked sum from both of these and doesn't adjust them. | |
| :param n_clicks: Unused number of times button has been clicked. | |
| :param sliders: Prescribed slider values to set to sum to 1. | |
| :param year: Selected context year. | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :param locked: Which sliders to not consider in calculation. | |
| :return: Slider values scaled down to fit percentage of land used in context. | |
| """ | |
| context = df.loc[year, lat, lon] | |
| presc = pd.Series(sliders, index=constants.RECO_COLS) | |
| old_sum = context[constants.RECO_COLS].sum() | |
| new_sum = presc.sum() | |
| # TODO: There is certainly a more elegant way to handle this. | |
| if locked: | |
| unlocked = [col for col in constants.RECO_COLS if col not in locked] | |
| locked_sum = presc[locked].sum() | |
| old_sum -= locked_sum | |
| new_sum -= locked_sum | |
| # We do this to avoid divide by zero. In the case where new_sum == 0 | |
| # we have all locked columns and/or zero columns so no adjustment is needed | |
| if new_sum != 0: | |
| presc[unlocked] = presc[unlocked].div(new_sum).mul(old_sum) | |
| else: | |
| presc = presc.div(new_sum).mul(old_sum) | |
| # Set all negative values to 0 | |
| presc[presc < 0] = 0 | |
| return presc.tolist() | |
| def predict(n_clicks, year, lat, lon, sliders, predictor_name): | |
| """ | |
| Predicts ELUC from context and prescription stores. | |
| :param n_clicks: Unused number of times button has been clicked. | |
| :param year: Selected context year. | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :param sliders: Prescribed slider values. | |
| :param predictor_name: String name of predictor to use from dropdown. | |
| :return: Predicted ELUC. | |
| """ | |
| context = df.loc[year, lat, lon] | |
| presc = pd.Series(sliders, index=constants.RECO_COLS) | |
| # Preprocess presc into diffs | |
| presc = presc.combine_first(context[constants.NO_CHANGE_COLS]) | |
| diff = presc[constants.LAND_USE_COLS] - context[constants.LAND_USE_COLS] | |
| diff = diff.rename(constants.COLS_MAP) | |
| diff_df = pd.DataFrame([diff]) | |
| predictor = predictors[predictor_name] | |
| eluc = predictor.predict(diff_df) | |
| return f"{eluc:.4f}" | |
| def update_trivia(eluc_str, year, lat, lon): | |
| """ | |
| Updates trivia section based on rounded ELUC value. | |
| :param eluc_str: ELUC in string form. | |
| :param year: Selected context year. | |
| :param lat: Selected context lat. | |
| :param lon: Selected context lon. | |
| :return: Trivia string output. | |
| """ | |
| context = df.loc[year, lat, lon] | |
| area = context["cell_area"] | |
| # Calculate total reduction | |
| eluc = float(eluc_str) | |
| total_reduction = eluc * area | |
| return f"{-1 * total_reduction:,.2f} tonnes CO2", \ | |
| f"{-1 * total_reduction // constants.CO2_JFK_GVA:,.0f} tickets", \ | |
| f"{-1 * total_reduction // constants.CO2_PERSON:,.0f} people" | |
| 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) | |
| *Note: the prescriptor model is currently only trained on Western Europe* | |
| '''), | |
| dcc.Markdown('''## Context'''), | |
| html.Div([ | |
| dcc.Graph(id="map", figure=map_fig, style={"grid-column": "1"}), | |
| html.Div([context_div], style={"grid-column": "2"}), | |
| html.Div([legend_div], style={"grid-column": "3"}) | |
| ], style={"display": "grid", "grid-template-columns": "auto 1fr auto", 'position': 'relative'}), | |
| dcc.Markdown('''## Actions'''), | |
| html.Div([ | |
| html.Div([presc_select_div], style={"grid-column": "1"}), | |
| html.Div([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(checklist_div, style={"grid-column": "1", "height": "100%"}), | |
| html.Div(sliders_div, style={'grid-column': '2'}), | |
| dcc.Graph(id='context-fig', figure=utils.create_treemap(type_context=True), style={'grid-column': '3'}), | |
| dcc.Graph(id='presc-fig', figure=utils.create_treemap(type_context=False), style={'grid-clumn': '4'}) | |
| ], style={'display': 'grid', 'grid-template-columns': 'auto 40% 1fr 1fr', "width": "100%"}), | |
| html.Div([ | |
| frozen_div, | |
| html.Button("Sum to 100%", id='sum-button', n_clicks=0), | |
| html.Div(id='sum-warning') | |
| ]), | |
| dcc.Markdown('''## Outcomes'''), | |
| predict_div, | |
| dcc.Markdown('''## Trivia'''), | |
| trivia_div, | |
| dcc.Markdown('''## References'''), | |
| references_div | |
| ], style={'padding-left': '10px'},) | |
| if __name__ == '__main__': | |
| app.run_server(host='0.0.0.0', debug=False, port=4057, use_reloader=False, threaded=False) | |