def osi_download_button_pressed(self): return ( Output(self.dashboard_home.download_excel, "data"), Input(self.dashboard_home.download_excel_button, "n_clicks"), )
import dash_bootstrap_components as dbc from dash import Input, Output, html list_group = html.Div([ dbc.ListGroup([ dbc.ListGroupItem("Internal link", href="/l/components/list_group"), dbc.ListGroupItem("External link", href="https://google.com"), dbc.ListGroupItem("Disabled link", href="https://google.com", disabled=True), dbc.ListGroupItem("Button", id="button-item", n_clicks=0, action=True), ]), html.P(id="counter"), ]) @app.callback(Output("counter", "children"), [Input("button-item", "n_clicks")]) def count_clicks(n): return f"Button clicked {n} times"
def distribution_controllers(get_uuid: Callable, volumemodel: InplaceVolumesModel) -> None: @callback( Output({ "id": get_uuid("main-voldist"), "page": "custom" }, "children"), Input(get_uuid("selections"), "data"), State(get_uuid("page-selected"), "data"), ) def _update_page_custom(selections: dict, page_selected: str) -> tuple: if page_selected != "custom": raise PreventUpdate selections = selections[page_selected] if not selections["update"]: raise PreventUpdate selected_data = [ selections[x] for x in ["Subplots", "Color by", "X Response", "Y Response"] if selections[x] is not None ] groups = ["REAL"] parameters = [] for item in selected_data: if item in volumemodel.selectors and item not in groups: groups.append(item) if item in volumemodel.parameters and item not in parameters: parameters.append(item) # for bo/bg the data should be grouped on fluid zone if any(x in selected_data for x in ["BO", "BG"]) and "FLUID_ZONE" not in groups: if "BO" in selected_data and "BG" in selected_data: return html.Div("Can't plot BO against BG", style={"margin-top": "40px"}) selections["filters"]["FLUID_ZONE"] = [ "oil" if "BO" in selected_data else "gas" ] dframe = volumemodel.get_df(filters=selections["filters"], groups=groups, parameters=parameters) if dframe.empty: return html.Div("No data left after filtering", style={"margin-top": "40px"}) df_for_figure = (dframe if not (selections["Plot type"] == "bar" and not "REAL" in selected_data) else dframe.groupby([x for x in groups if x != "REAL"]).mean().reset_index()) figure = ( create_figure( plot_type=selections["Plot type"], data_frame=df_for_figure, x=selections["X Response"], y=selections["Y Response"], nbins=selections["hist_bins"], facet_col=selections["Subplots"], color=selections["Color by"], color_discrete_sequence=selections["Colorscale"], color_continuous_scale=selections["Colorscale"], barmode=selections["barmode"], boxmode=selections["barmode"], layout=dict(title=dict( text=(f"{volume_description(selections['X Response'])}" + (f" [{volume_unit(selections['X Response'])}]" if selections["X Response"] in volumemodel.volume_columns else "")), x=0.5, xref="paper", font=dict(size=18), ), ), yaxis=dict(showticklabels=True), ).add_annotation( fluid_annotation(selections)).update_xaxes({ "matches": None } if not selections["X axis matches"] else {}).update_yaxes({ "matches": None } if not selections["Y axis matches"] else {}).update_xaxes( { "type": "category", "tickangle": 45, "tickfont_size": 12 } if selections["X Response"] in volumemodel.selectors else {})) return custom_plotting_layout( figure=figure, tables=make_tables( dframe=dframe, responses=list( {selections["X Response"], selections["Y Response"]}), groups=groups, volumemodel=volumemodel, page_selected=page_selected, selections=selections, table_type="Statistics table", view_height=37, ) if selections["bottom_viz"] == "table" else None, ) @callback( Output({ "id": get_uuid("main-table"), "page": "table" }, "children"), Input(get_uuid("selections"), "data"), State(get_uuid("page-selected"), "data"), ) def _update_page_tables( selections: dict, page_selected: str, ) -> list: if page_selected != "table": raise PreventUpdate selections = selections[page_selected] if not selections["update"]: raise PreventUpdate table_groups = (["ENSEMBLE", "REAL"] if selections["Table type"] == "Statistics table" else ["ENSEMBLE"]) if selections["Group by"] is not None: table_groups.extend( [x for x in selections["Group by"] if x not in table_groups]) dframe = volumemodel.get_df(filters=selections["filters"], groups=table_groups) return make_tables( dframe=dframe, responses=selections["table_responses"], groups=selections["Group by"], view_height=85, table_type=selections["Table type"], volumemodel=volumemodel, page_selected=page_selected, selections=selections, ) @callback( Output({ "id": get_uuid("main-voldist"), "page": "per_zr" }, "children"), Input(get_uuid("selections"), "data"), State(get_uuid("page-selected"), "data"), ) def _update_page_per_zr(selections: dict, page_selected: str) -> list: if page_selected != "per_zr": raise PreventUpdate selections = selections[page_selected] if not selections["update"]: raise PreventUpdate figs = [] selectors = [ x for x in ["ZONE", "REGION", "FACIES", "FIPNUM", "SET"] if x in volumemodel.selectors ] color = selections["Color by"] is not None for selector in selectors: groups = list({selector, selections["Color by"] }) if color else [selector] dframe = volumemodel.get_df(filters=selections["filters"], groups=groups) piefig = ((create_figure( plot_type="pie", data_frame=dframe, values=selections["X Response"], names=selector, color_discrete_sequence=selections["Colorscale"], color=selector, ).update_traces( marker_line=dict(color="#000000", width=1)).update_layout( margin=dict(l=10, b=10))) if not color else []) barfig = create_figure( plot_type="bar", data_frame=dframe, x=selector, y=selections["X Response"], title=f"{selections['X Response']} per {selector}", barmode="overlay" if selector == selections["Color by"] else "group", layout={ "bargap": 0.05 }, color_discrete_sequence=selections["Colorscale"], color=selections["Color by"], text=selections["X Response"], xaxis=dict(type="category", tickangle=45, tickfont_size=17, title=None), ).update_traces( texttemplate=("%{text:.3s}" if selections["X Response"] in volumemodel.volume_columns else "%{text:.3g}"), textposition="auto", ) if selections["X Response"] not in volumemodel.hc_responses: barfig.add_annotation(fluid_annotation(selections)) figs.append([piefig, barfig]) return plots_per_zone_region_layout(figs) @callback( Output({ "id": get_uuid("main-voldist"), "page": "conv" }, "children"), Input(get_uuid("selections"), "data"), State(get_uuid("page-selected"), "data"), ) def _update_page_conv(selections: dict, page_selected: str) -> go.Figure: if page_selected != "conv": raise PreventUpdate selections = selections[page_selected] if not selections["update"]: raise PreventUpdate subplots = selections["Subplots"] if selections[ "Subplots"] is not None else [] groups = ["REAL"] if subplots and subplots not in groups: groups.append(subplots) dframe = volumemodel.get_df(filters=selections["filters"], groups=groups) dframe = dframe.sort_values(by=["REAL"]) if dframe.empty: return html.Div("No data left after filtering", style={"margin-top": "40px"}) dfs = [] df_groups = dframe.groupby(subplots) if subplots else [(None, dframe)] for _, df in df_groups: for calculation in ["mean", "p10", "p90"]: df_stat = df.reset_index(drop=True).copy() df_stat[selections["X Response"]] = ( (df_stat[selections["X Response"]].expanding().mean()) if calculation == "mean" else df_stat[selections["X Response"]].expanding().quantile( 0.1 if calculation == "p90" else 0.9)) df_stat["calculation"] = calculation df_stat["index"] = df_stat.index + 1 dfs.append(df_stat) if dfs: dframe = pd.concat(dfs) title = ( f"<b>Convergence plot of mean/p10/p90 for {selections['X Response']} </b>" " - shaded areas indicates failed/filtered out realizations") figure = (create_figure( plot_type="line", data_frame=dframe, x="REAL", y=selections["X Response"], facet_col=selections["Subplots"], color="calculation", custom_data=["calculation", "index"], title=title, yaxis=dict(showticklabels=True), ).update_traces( hovertemplate= (f"{selections['X Response']} %{{y}} <br>" f"%{{customdata[0]}} for realizations {dframe['REAL'].min()}-%{{x}}<br>" "Realization count: %{customdata[1]} <extra></extra>"), line_width=3.5, ).update_traces(line_color="black", selector={ "name": "mean" }).update_traces( line=dict(color="firebrick", dash="dash"), selector={ "name": "p10" }).update_traces( line=dict(color="royalblue", dash="dash"), selector={ "name": "p90" } ).update_xaxes({ "matches": None } if not selections["X axis matches"] else {}).update_yaxes( {"matches": None} if not selections["Y axis matches"] else {})) if selections["X Response"] not in volumemodel.hc_responses: figure.add_annotation(fluid_annotation(selections)) missing_reals = [ x for x in range(dframe["REAL"].min(), dframe["REAL"].max()) if x not in dframe["REAL"].unique() ] if missing_reals: for real_range in to_ranges(missing_reals): figure.add_vrect( x0=real_range[0] - 0.5, x1=real_range[1] + 0.5, fillcolor="gainsboro", layer="below", opacity=0.4, line_width=0, ) return convergence_plot_layout(figure=figure)
def test_dvcv004_duplicate_outputs_across_callbacks(dash_duo): app = Dash(__name__) app.layout = html.Div( [html.Div(id="a"), html.Div(id="b"), html.Div(id="c")]) @app.callback([Output("a", "children"), Output("a", "style")], [Input("b", "children")]) def x(b): return b, b @app.callback(Output("b", "children"), [Input("b", "style")]) def y(b): return b @app.callback(Output("a", "children"), [Input("b", "children")]) def x2(b): return b @app.callback([Output("b", "children"), Output("b", "style")], [Input("c", "children")]) def y2(c): return c @app.callback( [Output({"a": 1}, "children"), Output({ "b": ALL, "c": 1 }, "children")], [Input("b", "children")], ) def z(b): return b, b @app.callback( [ Output({"a": ALL}, "children"), Output({ "b": 1, "c": ALL }, "children") ], [Input("b", "children")], ) def z2(b): return b, b dash_duo.start_server(app, **debugging) specs = [ [ "Overlapping wildcard callback outputs", [ # depending on the order callbacks get reported to the # front end, either of these could have been registered first. # so we use this oder-independent form that just checks for # both prop_id's and the string "overlaps another output" '({"b":1,"c":ALL}.children)', "overlaps another output", '({"b":ALL,"c":1}.children)', "used in a different callback.", ], ], [ "Overlapping wildcard callback outputs", [ '({"a":ALL}.children)', "overlaps another output", '({"a":1}.children)', "used in a different callback.", ], ], [ "Duplicate callback outputs", ["Output 0 (b.children) is already in use."] ], [ "Duplicate callback outputs", ["Output 0 (a.children) is already in use."] ], ] check_errors(dash_duo, specs)
def test_dvcv010_bad_props(dash_duo): app = Dash(__name__) app.layout = html.Div( [ html.Div([html.Div(id="inner-div"), dcc.Input(id="inner-input")], id="outer-div"), dcc.Input(id={"a": 1}), ], id="main", ) @app.callback( Output("inner-div", "xyz"), # "data-xyz" is OK, does not give an error [Input("inner-input", "pdq"), Input("inner-div", "data-xyz")], [State("inner-div", "value")], ) def xyz(a, b, c): a if b else c @app.callback( Output({"a": MATCH}, "no"), [Input({"a": MATCH}, "never")], # "boo" will not error because we don't check State MATCH/ALLSMALLER [State({"a": MATCH}, "boo"), State({"a": ALL}, "nope")], ) def f(a, b, c): return a if b else c dash_duo.start_server(app, **debugging) specs = [ [ "Invalid prop for this component", [ 'Property "never" was used with component ID:', '{"a":1}', "in one of the Input items of a callback.", "This ID is assigned to a dash_core_components.Input component", "in the layout, which does not support this property.", "This ID was used in the callback(s) for Output(s):", '{"a":MATCH}.no', ], ], [ "Invalid prop for this component", [ 'Property "nope" was used with component ID:', '{"a":1}', "in one of the State items of a callback.", "This ID is assigned to a dash_core_components.Input component", '{"a":MATCH}.no', ], ], [ "Invalid prop for this component", [ 'Property "no" was used with component ID:', '{"a":1}', "in one of the Output items of a callback.", "This ID is assigned to a dash_core_components.Input component", '{"a":MATCH}.no', ], ], [ "Invalid prop for this component", [ 'Property "pdq" was used with component ID:', '"inner-input"', "in one of the Input items of a callback.", "This ID is assigned to a dash_core_components.Input component", "inner-div.xyz", ], ], [ "Invalid prop for this component", [ 'Property "value" was used with component ID:', '"inner-div"', "in one of the State items of a callback.", "This ID is assigned to a dash_html_components.Div component", "inner-div.xyz", ], ], [ "Invalid prop for this component", [ 'Property "xyz" was used with component ID:', '"inner-div"', "in one of the Output items of a callback.", "This ID is assigned to a dash_html_components.Div component", "inner-div.xyz", ], ], ] check_errors(dash_duo, specs)
def set_callbacks(self, app: Dash) -> None: @app.callback( Output(self.uuid("graph"), "figure"), Input(self.uuid("ensemble"), "value"), Input(self.uuid("plot_type"), "value"), Input(self.uuid("n_wells"), "value"), Input(self.uuid("wells"), "value"), Input(self.uuid("sort_by"), "value"), Input(self.uuid("stat_bars"), "value"), Input(self.uuid("ascending"), "value"), ) def _update_graph( ensemble: str, plot_type: str, n_wells: int, wells: Union[str, List[str]], sort_by: str, stat_bars: Union[str, List[str]], ascending: bool, ) -> dict: wells = wells if isinstance(wells, list) else [wells] stat_bars = stat_bars if isinstance(stat_bars, list) else [stat_bars] df = filter_df(df=self.smry, ensemble=ensemble, wells=wells) stat_df = (calc_statistics(df).sort_values( sort_by, ascending=ascending).iloc[0:n_wells, :]) traces = [] if plot_type == "Fan chart": traces.extend( _get_fanchart_traces( ens_stat_df=stat_df, color=self.ens_colors[ensemble], legend_group=ensemble, )) elif plot_type in ["Bar chart", "Line chart"]: for stat in stat_bars: yaxis = "y2" if stat == "count" else "y" if plot_type == "Bar chart": traces.append({ "x": [vec[5:] for vec in stat_df.index], # strip WBHP: "y": stat_df[stat], "name": [ key for key, value in self.label_map.items() if value == stat ][0], "yaxis": yaxis, "type": "bar", "offsetgroup": stat, "showlegend": True, }) elif plot_type == "Line chart": traces.append({ "x": [vec[5:] for vec in stat_df.index], # strip WBHP: "y": stat_df[stat], "name": [ key for key, value in self.label_map.items() if value == stat ][0], "yaxis": yaxis, "type": "line", "offsetgroup": stat, "showlegend": True, }) else: raise ValueError("Invalid plot type.") layout = self.theme.create_themed_layout({ "yaxis": { "side": "left", "title": "Bottom hole pressure", "showgrid": False, }, "yaxis2": { "side": "right", "overlaying": "y", "title": "Count (data points)", "showgrid": False, }, "xaxis": { "showgrid": False }, "barmode": "group", "legend": { "x": 1.05 }, }) return {"data": traces, "layout": layout} @app.callback( Output(self.uuid("select_stat"), "style"), Input(self.uuid("plot_type"), "value"), ) def _update_stat_selector(plot_type: str) -> dict: return ({ "display": "none" } if plot_type == "Fan chart" else { "display": "block" })
def well_overview_callbacks( app: Dash, get_uuid: Callable, data_models: Dict[str, EnsembleWellAnalysisData], theme: WebvizConfigTheme, ) -> None: @app.callback( Output(get_uuid(ClientsideStoreElements.WELL_OVERVIEW_CHART_SELECTED), "data"), Input( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_BUTTON), "button": ALL, }, "n_clicks", ), State( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_BUTTON), "button": ALL, }, "id", ), ) def _update_chart_selected(_apply_click: int, button_ids: list) -> str: """Stores the selected chart type in ClientsideStoreElements.WELL_OVERVIEW_CHART_SELECTED""" ctx = callback_context.triggered[0] # handle initial callback if ctx["prop_id"] == ".": return "bar" for button_id in button_ids: if button_id["button"] in ctx["prop_id"]: return button_id["button"] raise ValueError("Id not found") @callback( Output( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_BUTTON), "button": ALL, }, "style", ), Input(get_uuid(ClientsideStoreElements.WELL_OVERVIEW_CHART_SELECTED), "data"), State( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_BUTTON), "button": ALL, }, "id", ), ) def _update_button_style(chart_selected: str, button_ids: list) -> list: """Updates the styling of the chart type buttons, showing which chart type is currently selected. """ button_styles = { button["button"]: { "background-color": "#E8E8E8" } for button in button_ids } button_styles[chart_selected] = { "background-color": "#7393B3", "color": "#fff" } return update_relevant_components( id_list=button_ids, update_info=[{ "new_value": style, "conditions": { "button": button }, } for button, style in button_styles.items()], ) @callback( Output( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_SETTINGS), "charttype": ALL, }, "style", ), Input(get_uuid(ClientsideStoreElements.WELL_OVERVIEW_CHART_SELECTED), "data"), State( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_SETTINGS), "charttype": ALL, }, "id", ), ) def _display_charttype_settings(chart_selected: str, charttype_settings_ids: list) -> list: """Display only the settings relevant for the currently selected chart type.""" return [{ "display": "block" } if settings_id["charttype"] == chart_selected else { "display": "none" } for settings_id in charttype_settings_ids] @app.callback( Output(get_uuid(WellOverviewLayoutElements.GRAPH_FRAME), "children"), Input(get_uuid(WellOverviewLayoutElements.ENSEMBLES), "value"), Input( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_CHECKLIST), "charttype": ALL, }, "value", ), Input(get_uuid(WellOverviewLayoutElements.SUMVEC), "value"), Input(get_uuid(ClientsideStoreElements.WELL_OVERVIEW_CHART_SELECTED), "data"), Input(get_uuid(WellOverviewLayoutElements.WELL_FILTER), "value"), State( { "id": get_uuid(WellOverviewLayoutElements.CHARTTYPE_CHECKLIST), "charttype": ALL, }, "id", ), State(get_uuid(WellOverviewLayoutElements.GRAPH), "figure"), ) def _update_graph( ensembles: List[str], checklist_values: List[List[str]], sumvec: str, chart_selected: str, wells_selected: List[str], checklist_ids: List[Dict[str, str]], current_fig_dict: dict, ) -> List[wcc.Graph]: """Updates the well overview graph with selected input (f.ex chart type)""" ctx = callback_context.triggered[0]["prop_id"].split(".")[0] settings = { checklist_id["charttype"]: checklist_values[i] for i, checklist_id in enumerate(checklist_ids) } # If the event is a plot settings event, then we only update the formatting # and not the figure data if current_fig_dict is not None and is_plot_settings_event( ctx, get_uuid): fig_dict = format_well_overview_figure( go.Figure(current_fig_dict), chart_selected, settings[chart_selected], sumvec, ) else: figure = WellOverviewFigure( ensembles, data_models, sumvec, chart_selected, wells_selected, theme, ) fig_dict = format_well_overview_figure(figure.figure, chart_selected, settings[chart_selected], sumvec) return [ wcc.Graph( id=get_uuid(WellOverviewLayoutElements.GRAPH), style={"height": "87vh"}, figure=fig_dict, ) ]
def layout_controllers(get_uuid: Callable) -> None: @callback( Output(get_uuid("page-selected"), "data"), Output(get_uuid("voldist-page-selected"), "data"), Input({ "id": get_uuid("selections"), "button": ALL }, "n_clicks"), Input(get_uuid("tabs"), "value"), State({ "id": get_uuid("selections"), "button": ALL }, "id"), State(get_uuid("voldist-page-selected"), "data"), ) def _selected_page_controllers( _apply_click: int, tab_selected: str, button_ids: list, previous_page: dict, ) -> tuple: ctx = callback_context.triggered[0] initial_pages = {"voldist": "custom", "tornado": "torn_multi"} # handle initial callback if ctx["prop_id"] == ".": page_selected = (tab_selected if not tab_selected in initial_pages else initial_pages[tab_selected]) previous_page = initial_pages elif "tabs" in ctx["prop_id"]: page_selected = (tab_selected if not tab_selected in initial_pages else previous_page[tab_selected]) previous_page = no_update else: for button_id in button_ids: if button_id["button"] in ctx["prop_id"]: page_selected = previous_page[tab_selected] = button_id[ "button"] return page_selected, previous_page @callback( Output({ "id": get_uuid("selections"), "button": ALL }, "style"), Input(get_uuid("page-selected"), "data"), State(get_uuid("tabs"), "value"), State({ "id": get_uuid("selections"), "button": ALL }, "id"), ) def _update_button_style(page_selected: str, tab_selected: str, button_ids: list) -> list: if tab_selected not in ["voldist", "tornado"]: raise PreventUpdate button_styles = { button["button"]: { "background-color": "#E8E8E8" } for button in button_ids } button_styles[page_selected] = { "background-color": "#7393B3", "color": "#fff" } return update_relevant_components( id_list=button_ids, update_info=[{ "new_value": style, "conditions": { "button": button }, } for button, style in button_styles.items()], ) @callback( Output({ "id": get_uuid("main-voldist"), "page": ALL }, "style"), Input(get_uuid("page-selected"), "data"), State({ "id": get_uuid("main-voldist"), "page": ALL }, "id"), State(get_uuid("tabs"), "value"), ) def _main_voldist_display( page_selected: str, main_layout_ids: list, tab_selected: str, ) -> list: if tab_selected != "voldist": raise PreventUpdate voldist_layout = [] for page_id in main_layout_ids: if page_id["page"] == page_selected: voldist_layout.append({"display": "block"}) else: voldist_layout.append({"display": "none"}) return voldist_layout @callback( Output({ "id": get_uuid("main-tornado"), "page": ALL }, "style"), Input(get_uuid("page-selected"), "data"), State({ "id": get_uuid("main-tornado"), "page": ALL }, "id"), State(get_uuid("tabs"), "value"), ) def _main_tornado_display( page_selected: str, main_layout_ids: list, tab_selected: str, ) -> list: if tab_selected != "tornado": raise PreventUpdate main_layout = [] for page_id in main_layout_ids: if page_id["page"] == page_selected: main_layout.append({"display": "block"}) else: main_layout.append({"display": "none"}) return main_layout
def fipfile_qc_controller(get_uuid: Callable, disjoint_set_df: pd.DataFrame) -> None: @callback( Output(get_uuid("main-fipqc"), "children"), Input(get_uuid("selections"), "data"), Input({ "id": get_uuid("main-fipqc"), "element": "display-option" }, "value"), State(get_uuid("page-selected"), "data"), ) def _update_page_fipfileqc(selections: dict, display_option: str, page_selected: str) -> html.Div: ctx = callback_context.triggered[0] if page_selected != "fipqc": raise PreventUpdate df = disjoint_set_df[["SET", "FIPNUM", "REGION", "ZONE", "REGZONE"]] selections = selections[page_selected] if not "display-option" in ctx["prop_id"]: if not selections["update"]: raise PreventUpdate for filt, values in selections["filters"].items(): df = df.loc[df[filt].isin(values)] if selections["Group table"] and display_option == "table": df["FIPNUM"] = df["FIPNUM"].astype(str) df = df.groupby(["SET" ]).agg(lambda x: ", ".join(set(x))).reset_index() df = df.sort_values(by=["SET"]) if display_option == "table": return html.Div(children=create_data_table( columns=create_table_columns(df.columns), data=df.to_dict("records"), height="82vh", table_id={"table_id": "disjointset-info"}, style_cell_conditional=[ { "if": { "column_id": ["SET", "FIPNUM"] }, "width": "10%" }, { "if": { "column_id": ["ZONE", "REGION"] }, "width": "20%" }, ], style_cell={ "whiteSpace": "normal", "textAlign": "left", "height": "auto", }, ), ) df["FIPNUM"] = df["FIPNUM"].astype(str) return html.Div([ create_heatmap(df=df, y="ZONE", x="REGION"), create_heatmap(df=df, y="ZONE", x="FIPNUM"), create_heatmap(df=df, y="REGION", x="FIPNUM"), ])
import dash_bootstrap_components as dbc from dash import Input, Output input_group = dbc.InputGroup([ dbc.Button("Random name", id="input-group-button", n_clicks=0), dbc.Input(id="input-group-button-input", placeholder="name"), ]) @app.callback( Output("input-group-button-input", "value"), [Input("input-group-button", "n_clicks")], ) def on_button_click(n_clicks): if n_clicks: names = ["Arthur Dent", "Ford Prefect", "Trillian Astra"] which = n_clicks % len(names) return names[which] else: return ""
html.Div( id="toast-container", style={ "position": "fixed", "top": 10, "right": 10, "width": 350 }, ), ], className="p-5", ) @app.callback( Output("message-store", "data"), [ Input("button", "n_clicks"), Input({ "type": "toast", "id": ALL }, "n_dismiss"), ], [State("message-store", "data")], ) def manage_store(n, dismissed_toast, store): """ This callback manages the message store. If the "generate" button is clicked we add an item to the store. If one of the toasts is dismissed we remove the corresponding item from the store. To figure out which of these happened, we use callback context.
html.Br(), ], className='one columns'), html.Div(children=[ html.H3('NETWORK DIAGRAM'), dcc.Graph(id='Graph', figure=network_graph(df)), html.Br(), ], className='ten columns'), ], className='row') # Callback for adding the all check @app.callback( Output('port-checklist', 'value'), Output('all-checklist', 'value'), Input('port-checklist', 'value'), Input('all-checklist', 'value'), ) def sync_checklists(ports_selected, all_selected): ctx = callback_context input_id = ctx.triggered[0]['prop_id'].split('.')[0] if input_id == 'port-checklist': all_selected = ['All'] if set(ports_selected) == set(all_ports) else [] else: ports_selected = all_ports if all_selected else [] return ports_selected, all_selected # Callback to update the graph according to the checklist
), ], id="modal-body-scroll", scrollable=True, is_open=False, ), ] ) def toggle_modal(n1, n2, is_open): if n1 or n2: return not is_open return is_open app.callback( Output("modal-scroll", "is_open"), [Input("open-scroll", "n_clicks"), Input("close-scroll", "n_clicks")], [State("modal-scroll", "is_open")], )(toggle_modal) app.callback( Output("modal-body-scroll", "is_open"), [ Input("open-body-scroll", "n_clicks"), Input("close-body-scroll", "n_clicks"), ], [State("modal-body-scroll", "is_open")], )(toggle_modal)
def osi_reset_button_pressed(self): return ( Output(self.dashboard_home.confirm_reset_dialogue, "displayed"), Input(self.dashboard_home.reset_button, "n_clicks"), )
), ctl.H2("Boolean input"), bool_input, ctl.H2("Matrix input"), matrix_input, ctl.H2("Slider input"), slider_input, ctl.H2("Dynamic inputs"), ctl.Button("Generate inputs", id="generate-inputs"), html.Div(id="dynamic-inputs"), ctl.H1("Output"), html.Span(id="output"), ]) @app.callback(Output("output", "children"), Input(your_component.get_all_kwargs_id(), "value")) def show_outputs(*args): kwargs = your_component.reconstruct_kwargs_from_state() return str(kwargs) @app.callback(Output("dynamic-inputs", "children"), Input("generate-inputs", "n_clicks")) def add_inputs(n_clicks): if not n_clicks: raise PreventUpdate
), md={ "size": 12, "offset": 0 }, lg={ "size": 10, "offset": 1 }, )), ]) @callback( [ Output("btn-add-schedule", "hidden"), Output("btn-remove-schedule", "hidden"), Output("btn-add-schedule", "n_clicks"), Output("btn-remove-schedule", "n_clicks"), ], [ Input("btn-add-schedule", "n_clicks"), Input("btn-remove-schedule", "n_clicks"), Input("btn-start-scanning", "n_clicks"), ], ) def btn_schedule_click(add_click, remove_click, open_click): """Add scanner/screen schedule""" if add_click > 0: tg_wrapper._handler._check_scheduled_job() return True, False, 0, 0
def set_callbacks(self, app): @app.callback( Output(self.uuid("parcoords"), "figure"), self.parcoord_inputs, ) def _update_parcoord(ens, exc_inc, parameter_list, *opt_args): """Updates parallel coordinates plot Filter dataframe for chosen ensembles and parameters Call render_parcoord to render new figure """ # Ensure selected ensembles is a list ens = ens if isinstance(ens, list) else [ens] # Ensure selected parameters is a list parameter_list = (parameter_list if isinstance( parameter_list, list) else [parameter_list]) special_columns = ["ENSEMBLE", "REAL"] if exc_inc == "exc": parameterdf = self.parameterdf.drop(parameter_list, axis=1) elif exc_inc == "inc": parameterdf = self.parameterdf[special_columns + parameter_list] params = [ param for param in parameterdf.columns if param not in special_columns and param in self.parameter_columns ] mode = opt_args[0] if opt_args else "ensemble" # Need a default response response = "" if mode == "response": if len(ens) != 1: # Need to wait for update of ensemble selector to multi=False raise PreventUpdate df = parameterdf.loc[self.parameterdf["ENSEMBLE"] == ens[0]] response = opt_args[1] response_filter_values = opt_args[2:] if len( opt_args) > 2 else {} filteroptions = parresp.make_response_filters( response_filters=self.response_filters, response_filter_values=response_filter_values, ) responsedf = parresp.filter_and_sum_responses( self.responsedf, ens[0], response, filteroptions=filteroptions, aggregation=self.aggregation, ) # Renaming to make it clear in plot. responsedf.rename(columns={response: f"Response: {response}"}, inplace=True) df = pd.merge(responsedf, df, on=["REAL"]).drop(columns=special_columns) df[self.uuid("COLOR")] = df.apply( lambda row: self.ensembles.index(ens[0]), axis=1) else: # Filter on ensembles (ens) and active parameters (params), # adding the COLOR column to the columns to keep df = self.parameterdf[self.parameterdf["ENSEMBLE"].isin(ens)][ params + ["ENSEMBLE"]] df[self.uuid("COLOR")] = df.apply( lambda row: self.ensembles.index(row["ENSEMBLE"]), axis=1) return render_parcoord( df, self.theme, self.ens_colormap, self.uuid("COLOR"), self.ensembles, mode, params, response, ) @app.callback( [ Output(self.uuid("ensembles"), "multi"), Output(self.uuid("ensembles"), "value"), Output(self.uuid("view_response"), "style"), ], [Input(self.uuid("mode"), "value")], ) def _update_mode(mode: str): if mode == "ensemble": return True, self.ensembles, {"display": "none"} if mode == "response": return False, self.ensembles[0], {"display": "block"} # The error should never occur raise ValueError("ensemble and response are the only valid modes.")
from dash import Dash, dcc, html, Input, Output import dash_bootstrap_components as dbc app = Dash(__name__)# external_stylesheets=[dbc.themes.BOOTSTRAP] app.layout = html.Div([ html.H6("Change the value in the text box to see callbacks in action!"), html.Div([ "Input: ", dcc.Dropdown(['New York City', 'Montréal', 'San Francisco'], 'Montréal',id='my-input') ]), #html.Br(), html.Div(id='my-output'), ]) @app.callback( Output(component_id='my-output', component_property='children'), Input(component_id='my-input', component_property='value') ) def update_output_div(input_value): return f'Output: {input_value}' if __name__ == '__main__': app.run_server(debug=True)
def plugin_callbacks( get_uuid: Callable, ensemble_surface_providers: Dict[str, EnsembleSurfaceProvider], surface_server: SurfaceServer, ensemble_fault_polygons_providers: Dict[str, EnsembleFaultPolygonsProvider], fault_polygons_server: FaultPolygonsServer, map_surface_names_to_fault_polygons: Dict[str, str], well_picks_provider: Optional[WellPickProvider], fault_polygon_attribute: Optional[str], ) -> None: def selections(tab: str, colorselector: bool = False) -> Dict[str, str]: uuid = get_uuid(LayoutElements.SELECTIONS if not colorselector else LayoutElements.COLORSELECTIONS) return {"view": ALL, "id": uuid, "tab": tab, "selector": ALL} def selector_wrapper(tab: str, colorselector: bool = False) -> Dict[str, str]: uuid = get_uuid(LayoutElements.WRAPPER if not colorselector else LayoutElements.COLORWRAPPER) return {"id": uuid, "tab": tab, "selector": ALL} def links(tab: str, colorselector: bool = False) -> Dict[str, str]: uuid = get_uuid(LayoutElements.LINK if not colorselector else LayoutElements.COLORLINK) return {"id": uuid, "tab": tab, "selector": ALL} @callback( Output(get_uuid(LayoutElements.OPTIONS_DIALOG), "open"), Input({ "id": get_uuid("Button"), "tab": ALL }, "n_clicks"), State(get_uuid(LayoutElements.OPTIONS_DIALOG), "open"), ) def open_close_options_dialog(_n_click: list, is_open: bool) -> bool: if any(click is not None for click in _n_click): return not is_open raise PreventUpdate # 2nd callback @callback( Output({ "id": get_uuid(LayoutElements.LINKED_VIEW_DATA), "tab": MATCH }, "data"), Output(selector_wrapper(MATCH), "children"), Input(selections(MATCH), "value"), Input({ "id": get_uuid(LayoutElements.VIEWS), "tab": MATCH }, "value"), Input({ "id": get_uuid(LayoutElements.MULTI), "tab": MATCH }, "value"), Input(links(MATCH), "value"), Input(get_uuid(LayoutElements.REALIZATIONS_FILTER), "value"), Input(get_uuid("tabs"), "value"), State(selections(MATCH), "id"), State(selector_wrapper(MATCH), "id"), ) def _update_components_and_selected_data( mapselector_values: List[Dict[str, Any]], number_of_views: int, multi_selector: str, selectorlinks: List[List[str]], filtered_reals: List[int], tab_name: str, selector_ids: List[dict], wrapper_ids: List[dict], ) -> Tuple[List[dict], list]: """Reads stored raw selections, stores valid selections as a dcc.Store and updates visible and valid selections in layout""" # Prevent update if the pattern matched components does not match the current tab datatab = wrapper_ids[0]["tab"] if datatab != tab_name or number_of_views is None: raise PreventUpdate ctx = callback_context.triggered[0]["prop_id"] selector_values = [] for idx in range(number_of_views): view_selections = combine_selector_values_and_name( mapselector_values, selector_ids, view=idx) view_selections["multi"] = multi_selector if tab_name == Tabs.STATS: view_selections[ "mode"] = DefaultSettings.VIEW_LAYOUT_STATISTICS_TAB[idx] selector_values.append(view_selections) linked_selector_names = [l[0] for l in selectorlinks if l] component_properties = _update_selector_component_properties_from_provider( selector_values=selector_values, linked_selectors=linked_selector_names, multi=multi_selector, multi_in_ctx=get_uuid(LayoutElements.MULTI) in ctx, filtered_realizations=filtered_reals, ) # retrive the updated selector values from the component properties selector_values = [{key: val["value"] for key, val in data.items()} for idx, data in enumerate(component_properties)] if multi_selector is not None: selector_values = update_selections_with_multi( selector_values, multi_selector) selector_values = remove_data_if_not_valid(selector_values) if tab_name == Tabs.DIFF and len(selector_values) == 2: selector_values = add_diff_surface_to_values(selector_values) return ( selector_values, [ SideBySideSelectorFlex( tab_name, get_uuid, selector=id_val["selector"], view_data=[ data[id_val["selector"]] for data in component_properties ], link=id_val["selector"] in linked_selector_names, dropdown=id_val["selector"] in ["ensemble", "mode"], ) for id_val in wrapper_ids ], ) # 3rd callback @callback( Output( { "id": get_uuid(LayoutElements.VERIFIED_VIEW_DATA), "tab": MATCH }, "data"), Output(selector_wrapper(MATCH, colorselector=True), "children"), Input({ "id": get_uuid(LayoutElements.LINKED_VIEW_DATA), "tab": MATCH }, "data"), Input(selections(MATCH, colorselector=True), "value"), Input( { "view": ALL, "id": get_uuid(LayoutElements.RANGE_RESET), "tab": MATCH }, "n_clicks", ), Input(links(MATCH, colorselector=True), "value"), State({ "id": get_uuid(LayoutElements.MULTI), "tab": MATCH }, "value"), State(selector_wrapper(MATCH, colorselector=True), "id"), State(get_uuid(LayoutElements.STORED_COLOR_SETTINGS), "data"), State(get_uuid("tabs"), "value"), State(selections(MATCH, colorselector=True), "id"), ) def _update_color_components_and_value( selector_values: List[dict], colorvalues: List[Dict[str, Any]], _n_click: int, colorlinks: List[List[str]], multi: str, color_wrapper_ids: List[dict], stored_color_settings: Dict, tab: str, colorval_ids: List[dict], ) -> Tuple[List[dict], list]: """Adds color settings to validated stored selections, updates color component in layout and writes validated selectors with colors to a dcc.Store""" ctx = callback_context.triggered[0]["prop_id"] if selector_values is None: raise PreventUpdate reset_color_index = (json.loads(ctx.split(".")[0])["view"] if get_uuid( LayoutElements.RANGE_RESET) in ctx else None) color_update_index = (json.loads(ctx.split(".")[0]).get("view") if LayoutElements.COLORSELECTIONS in ctx else None) # if a selector is set as multi in the "Maps per Selector" tab # color_range should be linked and the min and max surface ranges # from all the views should be used as range use_range_from_all = tab == Tabs.SPLIT and multi != "attribute" links = [l[0] for l in colorlinks if l] links = links if not use_range_from_all else links + ["color_range"] # update selector_values with current values from the color components for idx, data in enumerate(selector_values): data.update( combine_selector_values_and_name(colorvalues, colorval_ids, view=idx)) color_component_properties = _update_color_component_properties( values=selector_values, links=links if not use_range_from_all else links + ["color_range"], stored_color_settings=stored_color_settings, reset_color_index=reset_color_index, color_update_index=color_update_index, ) if use_range_from_all: ranges = [data["surface_range"] for data in selector_values] min_max_for_all = [ min(r[0] for r in ranges), max(r[1] for r in ranges) ] for data in color_component_properties: data["color_range"]["range"] = min_max_for_all if reset_color_index is not None: data["color_range"]["value"] = min_max_for_all for idx, data in enumerate(color_component_properties): for key, val in data.items(): selector_values[idx][key] = val["value"] return ( selector_values, [ SideBySideSelectorFlex( tab, get_uuid, selector=id_val["selector"], view_data=[ data[id_val["selector"]] for data in color_component_properties ], link=id_val["selector"] in links, dropdown=id_val["selector"] in ["colormap"], ) for id_val in color_wrapper_ids ], ) # 4th callback @callback( Output(get_uuid(LayoutElements.STORED_COLOR_SETTINGS), "data"), Input({ "id": get_uuid(LayoutElements.VERIFIED_VIEW_DATA), "tab": ALL }, "data"), State(get_uuid("tabs"), "value"), State(get_uuid(LayoutElements.STORED_COLOR_SETTINGS), "data"), State({ "id": get_uuid(LayoutElements.VERIFIED_VIEW_DATA), "tab": ALL }, "id"), ) def _update_color_store( selector_values: List[List[dict]], tab: str, stored_color_settings: Dict[str, dict], data_id: List[dict], ) -> Dict[str, dict]: """Update the color store with chosen color range and colormap for surfaces""" if selector_values is None: raise PreventUpdate index = [x["tab"] for x in data_id].index(tab) stored_color_settings = (stored_color_settings if stored_color_settings is not None else {}) for data in selector_values[index]: surfaceid = (get_surface_id_for_diff_surf(selector_values[index]) if data.get("surf_type") == "diff" else get_surface_id_from_data(data)) stored_color_settings[surfaceid] = { "colormap": data["colormap"], "color_range": data["color_range"], } return stored_color_settings # 5th callback @callback( Output({ "id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH }, "layers"), Output({ "id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH }, "bounds"), Output({ "id": get_uuid(LayoutElements.DECKGLMAP), "tab": MATCH }, "views"), Input({ "id": get_uuid(LayoutElements.VERIFIED_VIEW_DATA), "tab": MATCH }, "data"), Input(get_uuid(LayoutElements.WELLS), "value"), Input(get_uuid(LayoutElements.VIEW_COLUMNS), "value"), Input(get_uuid(LayoutElements.OPTIONS), "value"), State(get_uuid("tabs"), "value"), State({ "id": get_uuid(LayoutElements.MULTI), "tab": MATCH }, "value"), ) def _update_map( surface_elements: List[dict], selected_wells: List[str], view_columns: Optional[int], options: List[str], tab_name: str, multi: str, ) -> tuple: """Updates the map component with the stored, validated selections""" # Prevent update if the pattern matched components does not match the current tab state_list = callback_context.states_list if state_list[1]["id"]["tab"] != tab_name or surface_elements is None: raise PreventUpdate if tab_name == Tabs.DIFF: view_columns = 3 if view_columns is None else view_columns layers = update_map_layers( views=len(surface_elements), include_well_layer=well_picks_provider is not None, visible_well_layer=LayoutLabels.SHOW_WELLS in options, visible_fault_polygons_layer=LayoutLabels.SHOW_FAULTPOLYGONS in options, visible_hillshading_layer=LayoutLabels.SHOW_HILLSHADING in options, ) layer_model = DeckGLMapLayersModel(layers) for idx, data in enumerate(surface_elements): diff_surf = data.get("surf_type") == "diff" surf_meta, img_url = ( get_surface_metadata_and_image(data) if not diff_surf else get_surface_metadata_and_image_for_diff_surface( surface_elements)) viewport_bounds = [ surf_meta.x_min, surf_meta.y_min, surf_meta.x_max, surf_meta.y_max, ] # Map3DLayer currently not implemented. Will replace # ColormapLayer and HillshadingLayer # layer_data = { # "mesh": img_url, # "propertyTexture": img_url, # "bounds": surf_meta.deckgl_bounds, # "rotDeg": surf_meta.deckgl_rot_deg, # "meshValueRange": [surf_meta.val_min, surf_meta.val_max], # "propertyValueRange": [surf_meta.val_min, surf_meta.val_max], # "colorMapName": data["colormap"], # "colorMapRange": data["color_range"], # } # layer_model.update_layer_by_id( # layer_id=f"{LayoutElements.MAP3D_LAYER}-{idx}", # layer_data=layer_data, # ) layer_data = { "image": img_url, "bounds": surf_meta.deckgl_bounds, "rotDeg": surf_meta.deckgl_rot_deg, "valueRange": [surf_meta.val_min, surf_meta.val_max], } layer_model.update_layer_by_id( layer_id=f"{LayoutElements.COLORMAP_LAYER}-{idx}", layer_data=layer_data, ) layer_model.update_layer_by_id( layer_id=f"{LayoutElements.HILLSHADING_LAYER}-{idx}", layer_data=layer_data, ) layer_model.update_layer_by_id( layer_id=f"{LayoutElements.COLORMAP_LAYER}-{idx}", layer_data={ "colorMapName": data["colormap"], "colorMapRange": data["color_range"], }, ) if (LayoutLabels.SHOW_FAULTPOLYGONS in options and fault_polygon_attribute is not None): # if diff surface use polygons from first view data_for_faultpolygons = data if not diff_surf else surface_elements[ 0] fault_polygons_provider = ensemble_fault_polygons_providers[ data_for_faultpolygons["ensemble"][0]] horizon_name = data_for_faultpolygons["name"][0] fault_polygons_address = SimulatedFaultPolygonsAddress( attribute=fault_polygon_attribute, name=map_surface_names_to_fault_polygons.get( horizon_name, horizon_name), realization=int(data_for_faultpolygons["realizations"][0]), ) layer_model.update_layer_by_id( layer_id=f"{LayoutElements.FAULTPOLYGONS_LAYER}-{idx}", layer_data={ "data": fault_polygons_server.encode_partial_url( provider_id=fault_polygons_provider.provider_id(), fault_polygons_address=fault_polygons_address, ), }, ) if LayoutLabels.SHOW_WELLS in options and well_picks_provider is not None: horizon_name = (data["name"][0] if not diff_surf else surface_elements[0]["name"][0]) layer_model.update_layer_by_id( layer_id=f"{LayoutElements.WELLS_LAYER}-{idx}", layer_data={ "data": well_picks_provider.get_geojson( selected_wells, horizon_name) }, ) return ( layer_model.layers, viewport_bounds if surface_elements else no_update, { "layout": view_layout(len(surface_elements), view_columns), "showLabel": True, "viewports": [ { "id": f"{view}_view", "show3D": False, "layerIds": [ # f"{LayoutElements.MAP3D_LAYER}-{view}", f"{LayoutElements.COLORMAP_LAYER}-{view}", f"{LayoutElements.HILLSHADING_LAYER}-{view}", f"{LayoutElements.FAULTPOLYGONS_LAYER}-{view}", f"{LayoutElements.WELLS_LAYER}-{view}", ], "name": make_viewport_label(surface_elements[view], tab_name, multi), } for view in range(len(surface_elements)) ], }, ) def make_viewport_label(data: dict, tab: str, multi: Optional[str]) -> str: """Return text-label for each viewport based on which tab is selected""" # For the difference view if tab == Tabs.DIFF and data.get("surf_type") == "diff": return "Difference Map (View1 - View2)" # For the statistics view 'mode' is used as label if tab == Tabs.STATS: if data["mode"] == SurfaceMode.REALIZATION: return f"REAL {data['realizations'][0]}" return data["mode"] # For the "map per selector" the chosen multi selector is used as label if tab == Tabs.SPLIT: if multi == "realizations": return f"REAL {data['realizations'][0]}" return data[multi][0] if multi == "mode" else data[multi] return general_label(data) def general_label(data: dict) -> str: """Create a general map label with all available information""" surfaceid = [ data["ensemble"][0], data["attribute"][0], data["name"][0] ] if data["date"]: surfaceid.append(data["date"][0]) if data["mode"] != SurfaceMode.REALIZATION: surfaceid.append(data["mode"]) else: surfaceid.append(f"REAL {data['realizations'][0]}") return " ".join(surfaceid) # pylint: disable=too-many-branches def _update_selector_component_properties_from_provider( selector_values: List[dict], linked_selectors: List[str], multi: str, multi_in_ctx: bool, filtered_realizations: List[int], ) -> List[Dict[str, dict]]: """Return updated options and values for the different selector components using the provider. If current selected value for a selector is in the options it will be used as the new value""" view_data = [] for idx, data in enumerate(selector_values): if not ("ensemble" in linked_selectors and idx > 0): ensembles = list(ensemble_surface_providers.keys()) ensemble = data.get("ensemble", []) ensemble = [ensemble] if isinstance(ensemble, str) else ensemble if not ensemble or multi_in_ctx: ensemble = ensembles if multi == "ensemble" else ensembles[: 1] if not ("attribute" in linked_selectors and idx > 0): attributes = [] for ens in ensemble: provider = ensemble_surface_providers[ens] attributes.extend([ x for x in provider.attributes() if x not in attributes ]) # only show attributes with date when multi is set to date if multi == "date": attributes = [ x for x in attributes if attribute_has_date(x, provider) ] attribute = [ x for x in data.get("attribute", []) if x in attributes ] if not attribute or multi_in_ctx: attribute = attributes if multi == "attribute" else attributes[: 1] if not ("name" in linked_selectors and idx > 0): names = [] for ens in ensemble: provider = ensemble_surface_providers[ens] for attr in attribute: attr_names = provider.surface_names_for_attribute(attr) names.extend([x for x in attr_names if x not in names]) name = [x for x in data.get("name", []) if x in names] if not name or multi_in_ctx: name = names if multi == "name" else names[:1] if not ("date" in linked_selectors and idx > 0): dates = [] for ens in ensemble: provider = ensemble_surface_providers[ens] for attr in attribute: attr_dates = provider.surface_dates_for_attribute(attr) if attr_dates is not None: dates.extend( [x for x in attr_dates if x not in dates]) interval_dates = [x for x in dates if "_" in x] dates = [x for x in dates if x not in interval_dates ] + interval_dates date = [x for x in data.get("date", []) if x in dates] if not date or multi_in_ctx: date = dates if multi == "date" else dates[:1] if not ("mode" in linked_selectors and idx > 0): modes = list(SurfaceMode) mode = data.get("mode", SurfaceMode.REALIZATION) if not ("realizations" in linked_selectors and idx > 0): if mode == SurfaceMode.REALIZATION: real = data.get("realizations", []) if not real or real[0] not in filtered_realizations: real = filtered_realizations[:1] else: real = filtered_realizations view_data.append({ "ensemble": { "value": ensemble, "options": ensembles, "multi": multi == "ensemble", }, "attribute": { "value": attribute, "options": attributes, "multi": multi == "attribute", }, "name": { "value": name, "options": names, "multi": multi == "name" }, "date": { "value": date, "options": dates, "multi": multi == "date" }, "mode": { "value": mode, "options": modes }, "realizations": { "value": real, "disabled": mode != SurfaceMode.REALIZATION, "options": filtered_realizations, "multi": mode != SurfaceMode.REALIZATION or multi == "realizations", }, }) return view_data def _update_color_component_properties( values: List[dict], links: List[str], stored_color_settings: dict, reset_color_index: Optional[int], color_update_index: Optional[int], ) -> List[dict]: """Return updated options and values for the different color selector components. If previous color settings are found it will be used, or set value to min and max surface range unless the user has updated the component through interaction.""" stored_color_settings = (stored_color_settings if stored_color_settings is not None else {}) colormaps = DefaultSettings.COLORMAP_OPTIONS surfids: List[str] = [] color_data: List[dict] = [] for idx, data in enumerate(values): surfaceid = (get_surface_id_for_diff_surf(values) if data.get("surf_type") == "diff" else get_surface_id_from_data(data)) # if surfaceid exist in another view use the color settings # from that view and disable the color component if surfaceid in surfids: index_of_first = surfids.index(surfaceid) surfids.append(surfaceid) view_data = deepcopy(color_data[index_of_first]) view_data["colormap"].update(disabled=True) view_data["color_range"].update(disabled=True) color_data.append(view_data) continue surfids.append(surfaceid) use_stored_color = (surfaceid in stored_color_settings and not color_update_index == idx) if not ("colormap" in links and idx > 0): colormap = (stored_color_settings[surfaceid]["colormap"] if use_stored_color else data.get( "colormap", colormaps[0])) if not ("color_range" in links and idx > 0): value_range = data["surface_range"] if data.get("colormap_keep_range", False): color_range = data["color_range"] elif reset_color_index == idx or surfaceid not in stored_color_settings: color_range = value_range else: color_range = ( stored_color_settings[surfaceid]["color_range"] if use_stored_color else data.get( "color_range", value_range)) color_data.append({ "colormap": { "value": colormap, "options": colormaps }, "color_range": { "value": color_range, "step": calculate_slider_step( min_value=value_range[0], max_value=value_range[1], steps=100, ) if value_range[0] != value_range[1] else 0, "range": value_range, }, }) return color_data def get_surface_address_from_data( data: dict, ) -> Union[SimulatedSurfaceAddress, ObservedSurfaceAddress, StatisticalSurfaceAddress]: """Return the SurfaceAddress based on view selection""" has_date = (ensemble_surface_providers[ data["ensemble"][0]].surface_dates_for_attribute( data["attribute"][0]) is not None) if data["mode"] == SurfaceMode.REALIZATION: return SimulatedSurfaceAddress( attribute=data["attribute"][0], name=data["name"][0], datestr=data["date"][0] if has_date else None, realization=int(data["realizations"][0]), ) if data["mode"] == SurfaceMode.OBSERVED: return ObservedSurfaceAddress( attribute=data["attribute"][0], name=data["name"][0], datestr=data["date"][0] if has_date else None, ) return StatisticalSurfaceAddress( attribute=data["attribute"][0], name=data["name"][0], datestr=data["date"][0] if has_date else None, realizations=[int(real) for real in data["realizations"]], statistic=data["mode"], ) def publish_and_get_surface_metadata( surface_provider: EnsembleSurfaceProvider, surface_address: SurfaceAddress) -> Tuple: provider_id: str = surface_provider.provider_id() qualified_address = QualifiedSurfaceAddress(provider_id, surface_address) surf_meta = surface_server.get_surface_metadata(qualified_address) if not surf_meta: # This means we need to compute the surface surface = surface_provider.get_surface(address=surface_address) if not surface: raise ValueError( f"Could not get surface for address: {surface_address}") surface_server.publish_surface(qualified_address, surface) surf_meta = surface_server.get_surface_metadata(qualified_address) return surf_meta, surface_server.encode_partial_url(qualified_address) def publish_and_get_diff_surface_metadata( surface_provider: EnsembleSurfaceProvider, surface_address: SurfaceAddress, sub_surface_provider: EnsembleSurfaceProvider, sub_surface_address: SurfaceAddress, ) -> Tuple: provider_id: str = surface_provider.provider_id() subprovider_id = sub_surface_provider.provider_id() qualified_address: Union[QualifiedSurfaceAddress, QualifiedDiffSurfaceAddress] qualified_address = QualifiedDiffSurfaceAddress( provider_id, surface_address, subprovider_id, sub_surface_address) surf_meta = surface_server.get_surface_metadata(qualified_address) if not surf_meta: surface_a = surface_provider.get_surface(address=surface_address) surface_b = sub_surface_provider.get_surface( address=sub_surface_address) if surface_a is not None and surface_b is not None: surface = surface_a - surface_b surface_server.publish_surface(qualified_address, surface) surf_meta = surface_server.get_surface_metadata(qualified_address) return surf_meta, surface_server.encode_partial_url(qualified_address) def get_surface_id_from_data(data: dict) -> str: """Retrieve surfaceid used for the colorstore""" surfaceid = data["attribute"][0] + data["name"][0] if data["date"]: surfaceid += data["date"][0] if data["mode"] == SurfaceMode.STDDEV: surfaceid += data["mode"] return surfaceid def get_surface_id_for_diff_surf(values: List[dict]) -> str: """Retrieve surfaceid used for the colorstore, for the diff surface this needs to be a combination of the two surfaces subtracted""" return get_surface_id_from_data(values[0]) + get_surface_id_from_data( values[1]) def update_selections_with_multi(values: List[dict], multi: str) -> List[dict]: """If a selector has been set as multi, the values selected in that component needs to be divided between the views so that there is only one unique value in each""" multi_values = values[0][multi] new_values = [] for val in multi_values: updated_values = deepcopy(values[0]) updated_values[multi] = [val] new_values.append(updated_values) return new_values def attribute_has_date(attribute: str, provider: EnsembleSurfaceProvider) -> bool: """Check if an attribute has any dates""" return provider.surface_dates_for_attribute(attribute) is not None def remove_data_if_not_valid(values: List[dict]) -> List[dict]: """Checks if surfaces can be provided from the selections. Any invalid selections are removed.""" updated_values = [] for data in values: surface_address = get_surface_address_from_data(data) try: provider = ensemble_surface_providers[data["ensemble"][0]] surf_meta, _ = publish_and_get_surface_metadata( surface_address=surface_address, surface_provider=provider, ) except ValueError: continue if not isinstance(surf_meta.val_min, np.ma.core.MaskedConstant) and not isinstance( surf_meta.val_max, np.ma.core.MaskedConstant): data["surface_range"] = [surf_meta.val_min, surf_meta.val_max] updated_values.append(data) return updated_values def get_surface_metadata_and_image(data: dict) -> Tuple: surface_address = get_surface_address_from_data(data) provider = ensemble_surface_providers[data["ensemble"][0]] return publish_and_get_surface_metadata( surface_address=surface_address, surface_provider=provider) def get_surface_metadata_and_image_for_diff_surface( selector_values: List[dict], ) -> Tuple: surface_address = get_surface_address_from_data(selector_values[0]) sub_surface_address = get_surface_address_from_data(selector_values[1]) provider = ensemble_surface_providers[selector_values[0]["ensemble"] [0]] sub_provider = ensemble_surface_providers[selector_values[1] ["ensemble"][0]] return publish_and_get_diff_surface_metadata( surface_address=surface_address, surface_provider=provider, sub_surface_address=sub_surface_address, sub_surface_provider=sub_provider, ) def add_diff_surface_to_values(selector_values: List[dict]) -> List[dict]: surf_meta, _ = get_surface_metadata_and_image_for_diff_surface( selector_values) selector_values.append({ "surface_range": [surf_meta.val_min, surf_meta.val_max], "surf_type": "diff", }) return selector_values def combine_selector_values_and_name(values: list, id_list: list, view: int) -> dict: """Combine selector values with selector name for given view""" return { id_values["selector"]: values for values, id_values in zip(values, id_list) if id_values["view"] == view }
toast = html.Div( [ dbc.Button( "Open toast", id="positioned-toast-toggle", color="primary", n_clicks=0, ), dbc.Toast( "This toast is placed in the top right", id="positioned-toast", header="Positioned toast", is_open=False, dismissable=True, icon="danger", # top: 66 positions the toast below the navbar style={"position": "fixed", "top": 66, "right": 10, "width": 350}, ), ] ) @app.callback( Output("positioned-toast", "is_open"), [Input("positioned-toast-toggle", "n_clicks")], ) def open_toast(n): if n: return True return False
def update_intersection_source(app: Dash, get_uuid: Callable, surface_geometry: Dict) -> None: @app.callback( Output({ "id": get_uuid("intersection-data"), "element": "well-wrapper" }, "style"), Output( { "id": get_uuid("intersection-data"), "element": "xline-wrapper" }, "style"), Output( { "id": get_uuid("intersection-data"), "element": "yline-wrapper" }, "style"), Input({ "id": get_uuid("intersection-data"), "element": "source" }, "value"), ) def _show_source_details(source: str) -> Tuple[Dict, Dict, Dict]: hide = {"display": "none"} show = {"display": "inline"} if source == "well": return show, hide, hide if source == "xline": return hide, show, hide if source == "yline": return hide, hide, show return hide, hide, hide @app.callback( Output( { "id": get_uuid("map"), "element": "stored_xline" }, "data", ), Input( { "id": get_uuid("intersection-data"), "cross-section": "xline", "element": "value", }, "value", ), ) def _store_xline(x_value: Union[float, int]) -> List: return [ [x_value, surface_geometry["ymin"]], [x_value, surface_geometry["ymax"]], ] @app.callback( Output( { "id": get_uuid("map"), "element": "stored_yline" }, "data", ), Input( { "id": get_uuid("intersection-data"), "cross-section": "yline", "element": "value", }, "value", ), ) def _store_yline(y_value: Union[float, int]) -> List: return [ [surface_geometry["xmin"], y_value], [surface_geometry["xmax"], y_value], ] @app.callback( Output( { "id": get_uuid("intersection-data"), "cross-section": MATCH, "element": "value", }, "step", ), Input( { "id": get_uuid("intersection-data"), "cross-section": MATCH, "element": "step", }, "value", ), ) def _set_cross_section_step(step: Union[float, int]) -> Union[float, int]: return step
[ dbc.AccordionItem( "This is the content of the first section. It has a " "default ID of item-0.", title="Item 1: item-0", ), dbc.AccordionItem( "This is the content of the second section. It has a " "default ID of item-1.", title="Item 2: item-1", ), dbc.AccordionItem( "This is the content of the third section. It has a " "default ID of item-2.", title="Item 3: item-2", ), ], id="accordion-always-open", always_open=True, ), html.Div(id="accordion-contents-open-ids", className="mt-3"), ]) @app.callback( Output("accordion-contents-open-ids", "children"), [Input("accordion-always-open", "active_item")], ) def change_item(item): return f"Item(s) selected: {item}"
def test_dvcv006_inconsistent_wildcards(dash_duo): app = Dash(__name__) app.layout = html.Div() @app.callback( [ Output({"b": MATCH}, "children"), Output({ "b": ALL, "c": 1 }, "children") ], [Input({ "b": MATCH, "c": 2 }, "children")], ) def x(c): return c, [c] @app.callback( [Output({"a": MATCH}, "children")], [ Input({"b": MATCH}, "children"), Input({"c": ALLSMALLER}, "children") ], [ State({ "d": MATCH, "dd": MATCH }, "children"), State({"e": ALL}, "children") ], ) def y(b, c, d, e): return b + c + d + e dash_duo.start_server(app, **debugging) specs = [ [ "`Input` / `State` wildcards not in `Output`s", [ 'State 0 ({"d":MATCH,"dd":MATCH}.children)', "has MATCH or ALLSMALLER on key(s) d, dd", 'where Output 0 ({"a":MATCH}.children)', ], ], [ "`Input` / `State` wildcards not in `Output`s", [ 'Input 1 ({"c":ALLSMALLER}.children)', "has MATCH or ALLSMALLER on key(s) c", 'where Output 0 ({"a":MATCH}.children)', ], ], [ "`Input` / `State` wildcards not in `Output`s", [ 'Input 0 ({"b":MATCH}.children)', "has MATCH or ALLSMALLER on key(s) b", 'where Output 0 ({"a":MATCH}.children)', ], ], [ "Mismatched `MATCH` wildcards across `Output`s", [ 'Output 1 ({"b":ALL,"c":1}.children)', "does not have MATCH wildcards on the same keys as", 'Output 0 ({"b":MATCH}.children).', ], ], ] check_errors(dash_duo, specs)
def test_cbva002_callback_return_validation(): app = Dash(__name__) app.layout = html.Div([ html.Div(id="a"), html.Div(id="b"), html.Div(id="c"), html.Div(id="d"), html.Div(id="e"), html.Div(id="f"), ]) @app.callback(Output("b", "children"), [Input("a", "children")]) def single(a): return set([1]) single_wrapped = app.callback_map["b.children"]["callback"] with pytest.raises(InvalidCallbackReturnValue): # outputs_list (normally callback_context.outputs_list) is provided # by the dispatcher from the request. single_wrapped("aaa", outputs_list={"id": "b", "property": "children"}) pytest.fail("not serializable") @app.callback([Output("c", "children"), Output("d", "children")], [Input("a", "children")]) def multi(a): return [1, set([2])] multi_wrapped = app.callback_map["..c.children...d.children.."]["callback"] with pytest.raises(InvalidCallbackReturnValue): outputs_list = [ { "id": "c", "property": "children" }, { "id": "d", "property": "children" }, ] multi_wrapped("aaa", outputs_list=outputs_list) pytest.fail("nested non-serializable") @app.callback([Output("e", "children"), Output("f", "children")], [Input("a", "children")]) def multi2(a): return ["abc"] multi2_wrapped = app.callback_map["..e.children...f.children.."][ "callback"] with pytest.raises(InvalidCallbackReturnValue): outputs_list = [ { "id": "e", "property": "children" }, { "id": "f", "property": "children" }, ] multi2_wrapped("aaa", outputs_list=outputs_list) pytest.fail("wrong-length list")
def multipage_app(validation=False): app = Dash(__name__, suppress_callback_exceptions=(validation == "suppress")) skeleton = html.Div( [dcc.Location(id="url", refresh=False), html.Div(id="page-content")]) layout_index = html.Div([ dcc.Link('Navigate to "/page-1"', id="index_p1", href="/page-1"), dcc.Link('Navigate to "/page-2"', id="index_p2", href="/page-2"), ]) layout_page_1 = html.Div([ html.H2("Page 1"), dcc.Input(id="input-1-state", type="text", value="Montreal"), dcc.Input(id="input-2-state", type="text", value="Canada"), html.Button(id="submit-button", n_clicks=0, children="Submit"), html.Div(id="output-state"), html.Br(), dcc.Link('Navigate to "/"', id="p1_index", href="/"), dcc.Link('Navigate to "/page-2"', id="p1_p2", href="/page-2"), ]) layout_page_2 = html.Div([ html.H2("Page 2"), dcc.Input(id="page-2-input", value="LA"), html.Div(id="page-2-display-value"), html.Br(), dcc.Link('Navigate to "/"', id="p2_index", href="/"), dcc.Link('Navigate to "/page-1"', id="p2_p1", href="/page-1"), ]) validation_layout = html.Div( [skeleton, layout_index, layout_page_1, layout_page_2]) def validation_function(): return skeleton if flask.has_request_context() else validation_layout app.layout = validation_function if validation == "function" else skeleton if validation == "attribute": app.validation_layout = validation_layout # Index callbacks @app.callback(Output("page-content", "children"), [Input("url", "pathname")]) def display_page(pathname): if pathname == "/page-1": return layout_page_1 elif pathname == "/page-2": return layout_page_2 else: return layout_index # Page 1 callbacks @app.callback( Output("output-state", "children"), [Input("submit-button", "n_clicks")], [State("input-1-state", "value"), State("input-2-state", "value")], ) def update_output(n_clicks, input1, input2): return ("The Button has been pressed {} times," 'Input 1 is "{}",' 'and Input 2 is "{}"').format(n_clicks, input1, input2) # Page 2 callbacks @app.callback(Output("page-2-display-value", "children"), [Input("page-2-input", "value")]) def display_value(value): print("display_value") return 'You have selected "{}"'.format(value) return app
), ], className="ms-1", ), dbc.Row(dbc.Col(footer)), ], fluid=True, ) """ ========================================================================== Callbacks """ @app.callback( Output("allocation_pie_chart", "figure"), Input("stock_bond", "value"), Input("cash", "value"), ) def update_pie(stocks, cash): bonds = 100 - stocks - cash slider_input = [cash, bonds, stocks] if stocks >= 70: investment_style = "Aggressive" elif stocks <= 30: investment_style = "Conservative" else: investment_style = "Moderate" figure = make_pie(slider_input, investment_style + " Asset Allocation") return figure
html.P("Mean:"), dcc.Slider(id="histograms-mean", min=-3, max=3, value=0, marks={ -3: "-3", 3: "3" }), html.P("Standard Deviation:"), dcc.Slider(id="histograms-std", min=1, max=3, value=1, marks={ 1: "1", 3: "3" }), ]) @callback( Output("histograms-graph", "figure"), Input("histograms-mean", "value"), Input("histograms-std", "value"), ) def display_color(mean, std): data = np.random.normal(mean, std, size=500) fig = px.histogram(data, nbins=30, range_x=[-10, 10]) return fig
def osi_update_notebook(self): return ( Output(self.notebook_home.notebook_div, "style"), Output(self.notebook_home.notebook, "data"), Input(self.notebook_home.update_notebook_button, "n_clicks"), )
import dash_bootstrap_components as dbc from dash import Input, Output, html, no_update toast = html.Div([ dbc.Button( "Open toast", id="simple-toast-toggle", color="primary", className="mb-3", n_clicks=0, ), dbc.Toast( [html.P("This is the content of the toast", className="mb-0")], id="simple-toast", header="This is the header", icon="primary", dismissable=True, is_open=False, ), ]) @app.callback( Output("simple-toast", "is_open"), [Input("simple-toast-toggle", "n_clicks")], ) def open_toast(n): if n == 0: return no_update return True
import dash_bootstrap_components as dbc from dash import Input, Output, State, html fade = html.Div([ dbc.Button("Toggle fade", id="fade-button", className="mb-3", n_clicks=0), dbc.Fade( dbc.Card( dbc.CardBody( html.P("This content fades in and out", className="card-text"))), id="fade", is_in=False, appear=False, ), ]) @app.callback( Output("fade", "is_in"), [Input("fade-button", "n_clicks")], [State("fade", "is_in")], ) def toggle_fade(n, is_in): if not n: # Button has never been clicked return False return not is_in