import time import dash_bootstrap_components as dbc from dash import Input, Output, html loading_spinner = html.Div( [ dbc.Button("Load", id="loading-button", n_clicks=0), dbc.Spinner(html.Div(id="loading-output")), ] ) @app.callback( Output("loading-output", "children"), [Input("loading-button", "n_clicks")] ) def load_output(n): if n: time.sleep(1) return f"Output loaded {n} times" return "Output not reloaded yet"
dbc.NavLink("Page 1", href="/page-1", active="exact"), dbc.NavLink("Page 2", href="/page-2", active="exact"), ], vertical=True, pills=True, ), ], style=SIDEBAR_STYLE, ) content = html.Div(id="page-content", style=CONTENT_STYLE) app.layout = html.Div([dcc.Location(id="url"), sidebar, content]) @app.callback(Output("page-content", "children"), [Input("url", "pathname")]) def render_page_content(pathname): if pathname == "/": return html.P("This is the content of the home page!") elif pathname == "/page-1": return html.P("This is the content of page 1. Yay!") elif pathname == "/page-2": return html.P("Oh cool, this is page 2!") # If the user tries to reach a different page, return a 404 message return html.Div( [ html.H1("404: Not found", className="text-danger"), html.Hr(), html.P(f"The pathname {pathname} was not recognised..."), ], className="p-3 bg-light rounded-3",
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"]: val for val, id_values in zip(values, id_list) if id_values["view"] == view }
H5("We are also always on the lookout for donations of medical equipment and supplies. If you can help in this area please contact us at [email protected]" ), ], width=7, ), ], justify="center", className="mt-5 mb-2", ), ], fluid=True, ) # top left bar graph ****************************************************** @callback(Output("bar-col", "children"), Input("bar-data", "value")) def create_bar_graph(data_column): bar_grpah = dcc.Graph( figure=px.bar(dfc, x=data_column, y="Patients turned away", hover_data=["Activity Date"]).update_xaxes( categoryorder="total descending")) return bar_grpah # top right map *********************************************************** @callback(Output("map-conslt-col", "children"), Input("map1-conslt-data", "value")) def create_map(data_column): map1 = dcc.Graph(
# Copied from the Dash documetation sample code at https://github.com/plotly/dash-recipes/tree/master/multi-page-app from dash import Input, Output, html, dcc import dash_bootstrap_components as dbc from multi_page_app.app import app layout = dbc.Container( fluid=True, children=[ html.Br(), html.H1('Fruit Selector'), dcc.Dropdown(id='app-2-dropdown', options=[{ 'label': '{}'.format(i), 'value': i } for i in ['Apple', 'Banana', 'Coconut', 'Date']]), html.Div(id='app-2-display-value') ]) @app.callback(Output('app-2-display-value', 'children'), Input('app-2-dropdown', 'value')) def display_value(value): return 'You have selected "{}"'.format(value)
app.layout = html.Div([ dcc.Input(id="input1", value="AAA"), html.Button(id="run-button1", children="Run"), html.Div(id="status1", children="Finished"), html.Div(id="result1", children="No results"), html.Hr(), dcc.Input(id="input2", value="aaa"), html.Button(id="run-button2", children="Run"), html.Div(id="status2", children="Finished"), html.Div(id="result2", children="No results"), ]) @app.long_callback( Output("result1", "children"), [Input("run-button1", "n_clicks"), State("input1", "value")], progress=Output("status1", "children"), progress_default="Finished", interval=500, cache_args_to_ignore=[0], ) def update_output1(set_progress, _n_clicks, value): for i in range(4): set_progress(f"Progress {i}/4") time.sleep(2) return f"Result for '{value}'" @app.long_callback( Output("result2", "children"),
def test_clsd013_clientside_callback_context_states_list(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ dcc.Input(id="in0"), dcc.Input(id={"in1": 0}), dcc.Input(id={"in1": 1}), dcc.Input(id={"in1": 2}), html.Div(id="output-clientside", style={"font-family": "monospace"}), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="states_list_to_str"), Output("output-clientside", "children"), [Input("in0", "n_submit"), Input({"in1": ALL}, "n_submit")], [State("in0", "value"), State({"in1": ALL}, "value")], ) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"in0","property":"value"},' '[{"id":{"in1":0},"property":"value"},' '{"id":{"in1":1},"property":"value"},' '{"id":{"in1":2},"property":"value"}]]'), ) dash_duo.find_element("#in0").send_keys("test 0" + Keys.RETURN) dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"in0","property":"value","value":"test 0"},' '[{"id":{"in1":0},"property":"value"},' '{"id":{"in1":1},"property":"value"},' '{"id":{"in1":2},"property":"value"}]]'), ) dash_duo.find_element("input[id*='in1\":0']").send_keys("test 1" + Keys.RETURN) dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"in0","property":"value","value":"test 0"},' '[{"id":{"in1":0},"property":"value","value":"test 1"},' '{"id":{"in1":1},"property":"value"},' '{"id":{"in1":2},"property":"value"}]]'), ) dash_duo.find_element("input[id*='in1\":2']").send_keys("test 2" + Keys.RETURN) dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"in0","property":"value","value":"test 0"},' '[{"id":{"in1":0},"property":"value","value":"test 1"},' '{"id":{"in1":1},"property":"value"},' '{"id":{"in1":2},"property":"value","value":"test 2"}]]'), )
def test_cdpr002_updatemodes(dash_dcc): app = Dash(__name__) app.layout = html.Div( [ dcc.DatePickerRange( id="date-picker-range", start_date_id="startDate", end_date_id="endDate", start_date_placeholder_text="Select a start date!", end_date_placeholder_text="Select an end date!", updatemode="bothdates", ), html.Div(id="date-picker-range-output"), ] ) @app.callback( Output("date-picker-range-output", "children"), [ Input("date-picker-range", "start_date"), Input("date-picker-range", "end_date"), ], ) def update_output(start_date, end_date): return "{} - {}".format(start_date, end_date) dash_dcc.start_server(app=app) start_date = dash_dcc.find_element("#startDate") start_date.click() end_date = dash_dcc.find_element("#endDate") end_date.click() assert ( dash_dcc.find_element("#date-picker-range-output").text == "None - None" ), "the output should not update when both clicked but no selection happen" start_date.click() dash_dcc.find_elements(dash_dcc.date_picker_day_locator)[4].click() assert ( dash_dcc.find_element("#date-picker-range-output").text == "None - None" ), "the output should not update when only one is selected" eday = dash_dcc.find_elements(dash_dcc.date_picker_day_locator)[-4] wait.until(lambda: eday.is_displayed() and eday.is_enabled(), timeout=2) eday.click() date_tokens = set(start_date.get_attribute("value").split("/")) date_tokens.update(end_date.get_attribute("value").split("/")) assert ( set( itertools.chain( *[ _.split("-") for _ in dash_dcc.find_element( "#date-picker-range-output" ).text.split(" - ") ] ) ) == date_tokens ), "date should match the callback output" assert dash_dcc.get_logs() == []
import dash dash.register_page(__name__) from dash import Dash, dcc, html, Input, Output, callback import plotly.express as px df = px.data.medals_wide(indexed=True) layout = html.Div( [ html.P("Medals included:"), dcc.Checklist( id="heatmaps-medals", options=[{"label": x, "value": x} for x in df.columns], value=df.columns.tolist(), ), dcc.Graph(id="heatmaps-graph"), ] ) @callback(Output("heatmaps-graph", "figure"), Input("heatmaps-medals", "value")) def filter_heatmap(cols): fig = px.imshow(df[cols]) return fig
def test_clsd002_chained_serverside_clientside_callbacks(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Label("x"), dcc.Input(id="x", value=3), html.Label("y"), dcc.Input(id="y", value=6), # clientside html.Label("x + y (clientside)"), dcc.Input(id="x-plus-y"), # server-side html.Label("x+y / 2 (serverside)"), dcc.Input(id="x-plus-y-div-2"), # server-side html.Div([ html.Label("Display x, y, x+y/2 (serverside)"), dcc.Textarea(id="display-all-of-the-values"), ]), # clientside html.Label("Mean(x, y, x+y, x+y/2) (clientside)"), dcc.Input(id="mean-of-all-values"), ]) app.clientside_callback( ClientsideFunction("clientside", "add"), Output("x-plus-y", "value"), [Input("x", "value"), Input("y", "value")], ) call_counts = {"divide": Value("i", 0), "display": Value("i", 0)} @app.callback(Output("x-plus-y-div-2", "value"), [Input("x-plus-y", "value")]) def divide_by_two(value): call_counts["divide"].value += 1 return float(value) / 2.0 @app.callback( Output("display-all-of-the-values", "value"), [ Input("x", "value"), Input("y", "value"), Input("x-plus-y", "value"), Input("x-plus-y-div-2", "value"), ], ) def display_all(*args): call_counts["display"].value += 1 return "\n".join([str(a) for a in args]) app.clientside_callback( ClientsideFunction("clientside", "mean"), Output("mean-of-all-values", "value"), [ Input("x", "value"), Input("y", "value"), Input("x-plus-y", "value"), Input("x-plus-y-div-2", "value"), ], ) dash_duo.start_server(app) test_cases = [ ["#x", "3"], ["#y", "6"], ["#x-plus-y", "9"], ["#x-plus-y-div-2", "4.5"], ["#display-all-of-the-values", "3\n6\n9\n4.5"], ["#mean-of-all-values", str((3 + 6 + 9 + 4.5) / 4.0)], ] for selector, expected in test_cases: dash_duo.wait_for_text_to_equal(selector, expected) assert call_counts["display"].value == 1 assert call_counts["divide"].value == 1 x_input = dash_duo.wait_for_element_by_css_selector("#x") x_input.send_keys("1") test_cases = [ ["#x", "31"], ["#y", "6"], ["#x-plus-y", "37"], ["#x-plus-y-div-2", "18.5"], ["#display-all-of-the-values", "31\n6\n37\n18.5"], ["#mean-of-all-values", str((31 + 6 + 37 + 18.5) / 4.0)], ] for selector, expected in test_cases: dash_duo.wait_for_text_to_equal(selector, expected) assert call_counts["display"].value == 2 assert call_counts["divide"].value == 2
style={'textAlign': 'center'}), # interval activated once/week or when page refreshed dcc.Interval(id='interval_db', interval=86400000 * 7, n_intervals=0), html.Div(id='mongo-datatable', children=[]), html.Div([ html.Div(id='pie-graph', className='five columns'), html.Div(id='hist-graph', className='six columns'), ], className='row'), dcc.Store(id='changed-cell') ]) # Display Datatable with data from Mongo database @app.callback(Output('mongo-datatable', component_property='children'), Input('interval_db', component_property='n_intervals')) def populate_datatable(n_intervals): # Convert the Collection (table) date to a pandas DataFrame df = pd.DataFrame(list(collection.find())) # Convert id from ObjectId to string so it can be read by DataTable df['_id'] = df['_id'].astype(str) print(df.head(20)) return [ dash_table.DataTable( id='our-table', data=df.to_dict('records'), columns=[{ 'id': p, 'name': p, 'editable': False
def set_callbacks(self, app: Dash) -> None: @app.callback( [ Output(self.tornadoplot.storage_id, "data"), Output(self.uuid("table"), "data"), Output(self.uuid("table"), "columns"), ], [ Input(i, "value") for sublist in [ [ self.uuid("ensemble"), self.uuid("response"), self.uuid("source"), ], list(self.selectors_id.values()), ] for i in sublist ], ) def _render_table_and_tornado( ensemble: str, response: str, source: str, *filters: Union[str, List[str]] ) -> Tuple[str, List[dict], List[dict]]: # Filter data data = filter_dataframe( self.volumes, self.selectors, ensemble, source, filters ) # Table data table, columns = calculate_table(data, response) # TornadoPlot input tornado = json.dumps( { "ENSEMBLE": ensemble, "data": data.groupby("REAL") .sum() .reset_index()[["REAL", response]] .values.tolist(), "response_name": response, "number_format": "#.4g", "unit": volume_unit(response), } ) return tornado, table, columns @app.callback( [ Output(self.uuid("graph-wrapper"), "children"), Output(self.uuid("volume_title"), "children"), ], [ Input(self.tornadoplot.click_id, "data"), Input(self.tornadoplot.high_low_storage_id, "data"), Input(self.uuid("plot-type"), "value"), ], [ State(i, "value") for sublist in [ [ self.uuid("ensemble"), self.uuid("response"), self.uuid("source"), ], list(self.selectors_id.values()), ] for i in sublist ], ) def _render_chart( tornado_click_data_str: Union[str, None], high_low_storage: dict, plot_type: str, ensemble: str, response: str, source: str, *filters: Union[str, List[str]], ) -> Tuple[Union[wcc.Graph, html.Div], str]: if callback_context.triggered is None: raise PreventUpdate tornado_click: Union[dict, None] = ( json.loads(tornado_click_data_str) if tornado_click_data_str else None ) ctx = callback_context.triggered[0]["prop_id"].split(".")[0] if tornado_click: if ( high_low_storage is not None and ctx == self.tornadoplot.high_low_storage_id ): # Tornado plot is updated without a click in the plot, updating reals: if tornado_click["sens_name"] in high_low_storage: tornado_click["real_low"] = high_low_storage[ tornado_click["sens_name"] ].get("real_low") tornado_click["real_high"] = high_low_storage[ tornado_click["sens_name"] ].get("real_high") else: # fallback in a case where chosen sens_name does not exist in updated # tornado plot. # (can this ever occur except when sens_name already is None?) tornado_click["sens_name"] = None tornado_click["real_low"] = [] tornado_click["real_high"] = [] # Filter data data = filter_dataframe( self.volumes, self.selectors, ensemble, source, filters ) # Volume title: volume_title = f"{volume_description(response)} [{volume_unit(response)}]" # Make Plotly figure layout = {} layout.update({"height": 600, "margin": {"l": 100, "b": 100}}) if plot_type == "Per realization": # One bar per realization layout.update( { "title": "Response per realization", "xaxis": {"title": "Realizations"}, "yaxis": {"title": volume_title}, } ) plot_data = data.groupby("REAL").sum().reset_index() if tornado_click: figure_data = [ { "y": plot_data[ plot_data["REAL"].isin(tornado_click["real_low"]) ][response], "x": plot_data[ plot_data["REAL"].isin(tornado_click["real_low"]) ]["REAL"], "tickformat": ".4s", "type": "bar", "showlegend": False, "name": "", }, { "y": plot_data[ plot_data["REAL"].isin(tornado_click["real_high"]) ][response], "x": plot_data[ plot_data["REAL"].isin(tornado_click["real_high"]) ]["REAL"], "tickformat": ".4s", "type": "bar", "showlegend": False, "name": "", }, { "y": plot_data[ ~plot_data["REAL"].isin( tornado_click["real_low"] + tornado_click["real_high"] ) ][response], "x": plot_data[ ~plot_data["REAL"].isin( tornado_click["real_low"] + tornado_click["real_high"] ) ]["REAL"], "tickformat": ".4s", "type": "bar", "marker": {"color": "grey"}, "showlegend": False, "name": "", }, ] else: figure_data = [ { "y": plot_data[response], "x": plot_data["REAL"], "tickformat": ".4s", "type": "bar", "marker": {"color": "grey"}, }, ] figure = wcc.Graph( config={"displayModeBar": False}, id=self.uuid("graph"), figure={ "data": figure_data, "layout": self.theme.create_themed_layout(layout), }, ) elif plot_type == "Per sensitivity case": # One box per sensitivity name layout.update( { "title": "Distribution for each sensitivity case", "yaxis": {"title": volume_title}, } ) figure = wcc.Graph( config={"displayModeBar": False}, id=self.uuid("graph"), figure={ "data": [ { "y": senscase_df.groupby("REAL") .sum() .reset_index()[response], "name": f"{sensname} ({senscase})", "type": "box", } for sensname, sensname_df in data.groupby(["SENSNAME"]) for senscase, senscase_df in sensname_df.groupby( ["SENSCASE"] ) ], "layout": self.theme.create_themed_layout(layout), }, ) elif plot_type == "Per sensitivity name": # One box per sensitivity name layout.update( { "title": "Distribution for each sensitivity name", "yaxis": {"title": volume_title}, } ) figure = wcc.Graph( config={"displayModeBar": False}, id=self.uuid("graph"), figure={ "data": [ { "y": sensname_df.groupby("REAL") .sum() .reset_index()[response], "name": f"{sensname}", "type": "box", } for sensname, sensname_df in data.groupby(["SENSNAME"]) ], "layout": self.theme.create_themed_layout(layout), }, ) else: # Should not occur unless plot_type options are changed figure = html.Div("Invalid plot type") return figure, volume_title
def update_maps( app: Dash, get_uuid: Callable, surface_set_models: Dict[str, SurfaceSetModel], well_set_model: WellSetModel, ) -> None: @app.callback( Output({"id": get_uuid("map"), "element": "label"}, "children"), Output(get_uuid("leaflet-map1"), "layers"), Output({"id": get_uuid("map2"), "element": "label"}, "children"), Output(get_uuid("leaflet-map2"), "layers"), Output({"id": get_uuid("map3"), "element": "label"}, "children"), Output(get_uuid("leaflet-map3"), "layers"), Input( { "id": get_uuid("map-settings"), "map_id": "map1", "element": "surfaceattribute", }, "value", ), Input( { "id": get_uuid("map-settings"), "map_id": "map2", "element": "surfaceattribute", }, "value", ), Input( { "id": get_uuid("map-settings"), "map_id": "map1", "element": "surfacename", }, "value", ), Input( { "id": get_uuid("map-settings"), "map_id": "map2", "element": "surfacename", }, "value", ), Input( {"id": get_uuid("map-settings"), "map_id": "map1", "element": "ensemble"}, "value", ), Input( {"id": get_uuid("map-settings"), "map_id": "map2", "element": "ensemble"}, "value", ), Input( { "id": get_uuid("map-settings"), "map_id": "map1", "element": "calculation", }, "value", ), Input( { "id": get_uuid("map-settings"), "map_id": "map2", "element": "calculation", }, "value", ), Input(get_uuid("leaflet-map1"), "switch"), Input(get_uuid("leaflet-map2"), "switch"), Input(get_uuid("leaflet-map3"), "switch"), Input( {"id": get_uuid("map-settings"), "map_id": "map1", "element": "options"}, "value", ), Input( {"id": get_uuid("map-settings"), "map_id": "map2", "element": "options"}, "value", ), Input(get_uuid("map-color-ranges"), "data"), Input( {"id": get_uuid("map-settings"), "settings": "compute_diff"}, "value", ), Input(get_uuid("realization-store"), "data"), Input({"id": get_uuid("intersection-data"), "element": "well"}, "value"), Input({"id": get_uuid("map"), "element": "stored_polyline"}, "data"), Input({"id": get_uuid("map"), "element": "stored_xline"}, "data"), Input({"id": get_uuid("map"), "element": "stored_yline"}, "data"), Input({"id": get_uuid("intersection-data"), "element": "source"}, "value"), State(get_uuid("leaflet-map1"), "layers"), State(get_uuid("leaflet-map2"), "layers"), State(get_uuid("leaflet-map3"), "layers"), ) # pylint: disable=too-many-arguments, too-many-locals, too-many-branches def _update_maps( surfattr_map: str, surfattr_map2: str, surfname_map: str, surfname_map2: str, ensemble_map: str, ensemble_map2: str, calc_map: str, calc_map2: str, shade_map: Dict[str, bool], shade_map2: Dict[str, bool], shade_map3: Dict[str, bool], options: List[str], options2: List[str], color_range_settings: Dict, compute_diff: List[str], real_list: List[str], wellname: Optional[str], polyline: Optional[List], xline: Optional[List], yline: Optional[List], source: str, current_map: List, current_map2: List, current_map3: List, ) -> Tuple[str, List, str, List, str, List]: """Generate Leaflet layers for the three map views""" realizations = [int(real) for real in real_list] ctx = callback_context.triggered[0] if "compute_diff" in ctx["prop_id"]: if not compute_diff: return ( no_update, no_update, no_update, no_update, no_update, [], ) # Check if map is already generated and should just be updated with polylines update_poly_only = bool( current_map and ( "stored_polyline" in ctx["prop_id"] or "stored_yline" in ctx["prop_id"] or "stored_xline" in ctx["prop_id"] ) ) if polyline is not None: poly_layer = create_leaflet_polyline_layer( polyline, name="Polyline", poly_id="random_line" ) for map_layers in [current_map, current_map2, current_map3]: map_layers = replace_or_add_map_layer( map_layers, "Polyline", poly_layer ) if xline is not None and source == "xline": xline_layer = create_leaflet_polyline_layer( xline, name="Xline", poly_id="x_line" ) for map_layers in [current_map, current_map2, current_map3]: map_layers = replace_or_add_map_layer(map_layers, "Xline", xline_layer) if yline is not None and source == "yline": yline_layer = create_leaflet_polyline_layer( yline, name="Yline", poly_id="y_line" ) for map_layers in [current_map, current_map2, current_map3]: map_layers = replace_or_add_map_layer(map_layers, "Yline", yline_layer) # If callback is triggered by polyline drawing, only update polyline if update_poly_only: return ( f"Surface A: {surfattr_map} - {surfname_map} - {ensemble_map} - {calc_map}", current_map, f"Surface B: {surfattr_map2} - {surfname_map2} - {ensemble_map2} - {calc_map2}", no_update, "Surface A-B", no_update, ) if wellname is not None: well = well_set_model.get_well(wellname) well_layer = make_well_layer(well, name=well.name) # If callback is triggered by well change, only update well layer if "well" in ctx["prop_id"] or ( "source" in ctx["prop_id"] and source == "well" ): for map_layers in [current_map, current_map2, current_map3]: map_layers = replace_or_add_map_layer( map_layers, "Well", well_layer ) return ( f"Surface A: {surfattr_map} - {surfname_map} - " f"{ensemble_map} - {calc_map}", current_map, f"Surface B: {surfattr_map2} - {surfname_map2} - " f"{ensemble_map2} - {calc_map2}", current_map2, "Surface A-B", no_update, ) # Calculate maps if calc_map in ["Mean", "StdDev", "Max", "Min", "P90", "P10"]: surface = surface_set_models[ensemble_map].calculate_statistical_surface( name=surfname_map, attribute=surfattr_map, calculation=calc_map, realizations=realizations, ) else: surface = surface_set_models[ensemble_map].get_realization_surface( name=surfname_map, attribute=surfattr_map, realization=int(calc_map) ) if calc_map2 in ["Mean", "StdDev", "Max", "Min", "P90", "P10"]: surface2 = surface_set_models[ensemble_map2].calculate_statistical_surface( name=surfname_map2, attribute=surfattr_map2, calculation=calc_map2, realizations=realizations, ) else: surface2 = surface_set_models[ensemble_map2].get_realization_surface( name=surfname_map2, attribute=surfattr_map2, realization=int(calc_map2) ) # Generate Leaflet layers update_controls = check_if_update_needed( ctx=ctx, current_maps=[current_map, current_map2], compute_diff=compute_diff, color_range_settings=color_range_settings, ) surface_layers = create_or_return_base_layer( update_controls, surface, current_map, shade_map, color_range_settings, map_id="map1", ) surface_layers2 = create_or_return_base_layer( update_controls, surface2, current_map2, shade_map2, color_range_settings, map_id="map2", ) try: surface3 = surface.copy() surface3.values = surface3.values - surface2.values diff_layers = ( [ SurfaceLeafletModel( surface3, name="surface3", apply_shading=shade_map3.get("value", False), ).layer ] if update_controls["diff_map"]["update"] else [] ) except ValueError: diff_layers = [] if wellname is not None: surface_layers.append(well_layer) surface_layers2.append(well_layer) if polyline is not None: surface_layers.append(poly_layer) if xline is not None and source == "xline": surface_layers.append(xline_layer) if yline is not None and source == "yline": surface_layers.append(yline_layer) if well_set_model is not None: if options is not None or options2 is not None: if "intersect_well" in options or "intersect_well" in options2: ### This is potentially a heavy task as it loads all wells into memory wells: List[xtgeo.Well] = list(well_set_model.wells.values()) if "intersect_well" in options and update_controls["map1"]["update"]: surface_layers.append( create_leaflet_well_marker_layer(wells, surface) ) if "intersect_well" in options2 and update_controls["map2"]["update"]: surface_layers2.append( create_leaflet_well_marker_layer(wells, surface2) ) return ( f"Surface A: {surfattr_map} - {surfname_map} - {ensemble_map} - {calc_map}", surface_layers if update_controls["map1"]["update"] else no_update, f"Surface B: {surfattr_map2} - {surfname_map2} - {ensemble_map2} - {calc_map2}", surface_layers2 if update_controls["map2"]["update"] else no_update, "Surface A-B", diff_layers if update_controls["diff_map"]["update"] else no_update, ) @app.callback( Output({"id": get_uuid("map"), "element": "stored_polyline"}, "data"), Input(get_uuid("leaflet-map1"), "polyline_points"), ) def _store_polyline_points( positions_yx: List[List[float]], ) -> Optional[List[List[float]]]: """Stores drawn in polyline in a dcc.Store. Reversing elements to reflect normal behaviour""" if positions_yx is not None: try: return [[pos[1], pos[0]] for pos in positions_yx] except TypeError: warnings.warn("Polyline for map is not valid format") return None raise PreventUpdate @app.callback( Output( {"id": get_uuid("intersection-data"), "element": "source"}, "value", ), Output( {"id": get_uuid("intersection-data"), "element": "well"}, "value", ), Input(get_uuid("leaflet-map1"), "clicked_shape"), Input(get_uuid("leaflet-map1"), "polyline_points"), ) def _update_from_map_click( clicked_shape: Optional[Dict], _polyline: List[List[float]], ) -> Tuple[str, Union[_NoUpdate, str]]: """Update intersection source and optionally selected well when user clicks a shape in map""" ctx = callback_context.triggered[0] if "polyline_points" in ctx["prop_id"]: return "polyline", no_update if clicked_shape is None: raise PreventUpdate if clicked_shape.get("id") == "random_line": return "polyline", no_update if clicked_shape.get("id") in well_set_model.well_names: return "well", clicked_shape.get("id") raise PreventUpdate @app.callback( Output(get_uuid("map-color-ranges"), "data"), Output( {"id": get_uuid("map-settings"), "colors": "map2_clip_min"}, "disabled", ), Output( {"id": get_uuid("map-settings"), "colors": "map2_clip_max"}, "disabled", ), Input( {"id": get_uuid("map-settings"), "colors": "map1_clip_min"}, "value", ), Input( {"id": get_uuid("map-settings"), "colors": "map1_clip_max"}, "value", ), Input( {"id": get_uuid("map-settings"), "colors": "map2_clip_min"}, "value", ), Input( {"id": get_uuid("map-settings"), "colors": "map2_clip_max"}, "value", ), Input( {"id": get_uuid("map-settings"), "colors": "sync_range"}, "value", ), ) def _color_range_options( clip_min_map1: Optional[float], clip_max_map1: Optional[float], clip_min_map2: Optional[float], clip_max_map2: Optional[float], sync_range: list, ) -> Tuple[Dict[str, Dict], bool, bool]: ctx = callback_context.triggered[0] return ( { "map1": { "color_range": [clip_min_map1, clip_max_map1], "update": "map1" in ctx["prop_id"], }, "map2": { "color_range": [clip_min_map2, clip_max_map2] if not sync_range else [clip_min_map1, clip_max_map1], "update": "map2" in ctx["prop_id"] or (sync_range and "map1" in ctx["prop_id"]) or ( "sync_range" in ctx["prop_id"] and [clip_min_map1, clip_max_map1] != [clip_min_map2, clip_max_map2] ), }, }, bool(sync_range), bool(sync_range), )
def test_inin026_graphs_in_tabs_do_not_share_state(dash_duo): app = Dash(__name__, suppress_callback_exceptions=True) app.layout = html.Div([ dcc.Tabs( id="tabs", children=[ dcc.Tab(label="Tab 1", value="tab1", id="tab1"), dcc.Tab(label="Tab 2", value="tab2", id="tab2"), ], value="tab1", ), # Tab content html.Div(id="tab_content"), ]) tab1_layout = [ html.Div([ dcc.Graph( id="graph1", figure={ "data": [{ "x": [1, 2, 3], "y": [5, 10, 6], "type": "bar" }] }, ) ]), html.Pre(id="graph1_info"), ] tab2_layout = [ html.Div([ dcc.Graph( id="graph2", figure={ "data": [{ "x": [4, 3, 2], "y": [5, 10, 6], "type": "bar" }] }, ) ]), html.Pre(id="graph2_info"), ] @app.callback(Output("graph1_info", "children"), Input("graph1", "clickData")) def display_hover_data(hover_data): return json.dumps(hover_data) @app.callback(Output("graph2_info", "children"), Input("graph2", "clickData")) def display_hover_data_2(hover_data): return json.dumps(hover_data) @app.callback(Output("tab_content", "children"), Input("tabs", "value")) def render_content(tab): return tab2_layout if tab == "tab2" else tab1_layout dash_duo.start_server(app) dash_duo.find_element("#graph1:not(.dash-graph--pending)").click() until(lambda: '"label": 2' in dash_duo.find_element("#graph1_info").text, timeout=3) dash_duo.find_element("#tab2").click() dash_duo.find_element("#graph2:not(.dash-graph--pending)").click() until(lambda: '"label": 3' in dash_duo.find_element("#graph2_info").text, timeout=3)
def set_callbacks(self, app): @app.callback( Output(self.uuid("output_graph"), component_property="children"), Input(self.uuid("filter1_id"), component_property="value"), Input(self.uuid("filter2_id"), component_property="value"), Input(self.uuid("choice_id"), component_property="value"), ) def _update_graph(input_filter_obs, input_filter_param, choiceplot): """Renders KS matrix (how much a parameter is changed from prior to posterior""" active_info = read_csv( get_path(self.input_dir / "active_obs_info.csv"), index_col=0 ) joint_ks = read_csv( get_path(self.input_dir / "ks.csv"), index_col=0 ).replace(np.nan, 0.0) input_filter_obs = _set_inputfilter(input_filter_obs) input_filter_param = _set_inputfilter(input_filter_param) listtoplot = _get_listtoplot(joint_ks, choiceplot) joint_ks_sorted = joint_ks.filter(items=listtoplot).sort_index(axis=1) xx_data = list( joint_ks_sorted.filter(like=input_filter_obs, axis=1).columns ) yy_data = list( joint_ks_sorted.filter(like=input_filter_param, axis=0).index ) zz_data = _get_zzdata(joint_ks_sorted, yy_data, xx_data, active_info) if not xx_data or not yy_data: raise PreventUpdate yall_obs_data = list( joint_ks_sorted.filter(like=input_filter_param, axis=0).index ) zall_obs_data = joint_ks.loc[yall_obs_data, ["All_obs"]].to_numpy() return wcc.Graph( id=self.uuid("heatmap_id"), figure={ "data": [ go.Heatmap( x=xx_data, y=yy_data, z=zz_data, type="heatmap", colorscale="YlGnBu", zmin=0, zmax=1, hoverinfo="text", text=_hovertext_list( xx_data, yy_data, zz_data, active_info ), ), go.Heatmap( x=["All_obs"], y=yall_obs_data, z=zall_obs_data, type="heatmap", colorscale="YlGnBu", zmin=0, zmax=1, hoverinfo="text", text=_hovertext_list( ["All_obs"], yall_obs_data, zall_obs_data, active_info ), xaxis="x2", ), ], "layout": self.theme.create_themed_layout( { "title": "KS Matrix (degree of parameter change prior to posterior)", "xaxis": { "title": "Observations", "ticks": "", "domain": [0.0, 0.9], "showticklabels": True, "tickangle": 30, "automargin": True, }, "yaxis": { "title": "Parameters", "ticks": "", "showticklabels": True, "tickangle": -30, "automargin": True, }, "xaxis2": {"ticks": "", "domain": [0.95, 1.0]}, "plot_bgcolor": "grey", } ), }, style={"height": "45vh"}, clickData={"points": [{"x": xx_data[0], "y": yy_data[0]}]}, ) @app.callback( Output(self.uuid("click_data"), component_property="children"), Input(self.uuid("heatmap_id"), component_property="clickData"), Input(self.uuid("choice_hist_id"), component_property="value"), prevent_initial_call=True, ) def _display_click_data(celldata, hist_display): """render a histogram of parameters distribution prior/posterior or an average delta map prior-posterior.""" obs = celldata["points"][0]["x"] param = celldata["points"][0]["y"] active_info = read_csv( get_path(self.input_dir / "active_obs_info.csv"), index_col=0 ) if "FIELD" in param: fieldparam = param.replace("FIELD_", "") mygrid_ok_short = read_csv( get_path( Path(str(self.input_dir).replace("scalar_", "field_")) / f"delta_field{fieldparam}.csv" ) ) maxinput = mygrid_ok_short.filter(like="Mean_").max(axis=1) deltadata = "Mean_D_" + obs return wcc.Graph( id="2Dmap_avgdelta", figure=px.scatter( mygrid_ok_short, x="X_UTME", y="Y_UTMN", color=deltadata, range_color=[0, maxinput.max()], color_continuous_scale="Rainbow", opacity=0.9, title=f"Mean_delta_posterior-prior {obs}, {param}", hover_data=[ "X_UTME", "Y_UTMN", "Z_TVDSS", "IX", "JY", deltadata, ], ), ) post_df = read_csv(get_path(self.input_dir / f"{obs}.csv")) prior_df = read_csv(get_path(self.input_dir / "prior.csv")) if "TRANS" in hist_display: paraml = [ele for ele in prior_df.keys() if f"_{param}" in ele] if paraml != []: param = paraml[0] fig = go.Figure() fig.add_trace(go.Histogram(x=prior_df[param], name="prior", nbinsx=10)) fig.add_trace(go.Histogram(x=post_df[param], name="update", nbinsx=10)) fig.update_layout( self.theme.create_themed_layout( { "title": ( "Parameter distribution for observation " f"{obs} ({active_info.at['ratio', obs]})" ), "bargap": 0.2, "bargroupgap": 0.1, "xaxis": {"title": param}, } ) ) return wcc.Graph(id="lineplots", style={"height": "45vh"}, figure=fig) @app.callback( Output(self.uuid("generate_table"), component_property="children"), Input(self.uuid("choice_id"), component_property="value"), ) def _generatetable(choiceplot, max_rows=10): """Generate output table of data in KS matrix plot""" misfit_info = read_csv( get_path(self.input_dir / "misfit_obs_info.csv"), index_col=0 ) list_ok = list(misfit_info.filter(like="All_obs", axis=1).columns) listtoplot = [ele for ele in misfit_info.columns if ele not in list_ok] if choiceplot == "ALL": listtoplot = list_ok active_info = read_csv( get_path(self.input_dir / "active_obs_info.csv"), index_col=0, ) joint_ks = read_csv( get_path(self.input_dir / "ks.csv"), index_col=0, ).replace(np.nan, 0.0) ks_filtered = _get_ks_filtered( listtoplot, active_info, misfit_info, joint_ks, self.ks_filter ) ks_filtered = ks_filtered.sort_values(by="Ks_value", ascending=False) return dash_table.DataTable( columns=[{"name": i, "id": i} for i in ks_filtered.columns], editable=True, style_data_conditional=[ { "if": { "filter_query": "{Active Obs}=0", "column_id": "Active Obs", }, "backgroundColor": "grey", "color": "white", }, ], data=ks_filtered.to_dict("records"), sort_action="native", filter_action="native", page_action="native", page_current=0, page_size=max_rows, )
colormap_dropdown, html.Img(id="colormap-img", ), ], ), wcc.Frame( style={ "flex": 10, "height": "90vh" }, children=[map_obj], ), ]) @callback( Output("colormap-img", "src"), Input("colormap-select", "value"), ) def update_img(value): return value @callback( Output("deckgl-map", "layers"), Input("colormap-select", "value"), State("deckgl-map", "layers"), ) def _update_layers(colormap, deckgl_layers): if not colormap: return None def apply_colormap(layers): # Update the colormap layer then return the updated layers.
def test_cblp001_radio_buttons_callbacks_generating_children(dash_duo): TIMEOUT = 2 with open(os.path.join(os.path.dirname(__file__), "state_path.json")) as fp: EXPECTED_PATHS = json.load(fp) app = Dash(__name__) app.layout = html.Div([ dcc.RadioItems( options=[ { "label": "Chapter 1", "value": "chapter1" }, { "label": "Chapter 2", "value": "chapter2" }, { "label": "Chapter 3", "value": "chapter3" }, { "label": "Chapter 4", "value": "chapter4" }, { "label": "Chapter 5", "value": "chapter5" }, ], value="chapter1", id="toc", ), html.Div(id="body"), ]) for script in dcc._js_dist: app.scripts.append_script(script) chapters = { "chapter1": html.Div([ html.H1("Chapter 1", id="chapter1-header"), dcc.Dropdown( options=[{ "label": i, "value": i } for i in ["NYC", "MTL", "SF"]], value="NYC", id="chapter1-controls", ), html.Label(id="chapter1-label"), dcc.Graph(id="chapter1-graph"), ]), # Chapter 2 has the some of the same components in the same order # as Chapter 1. This means that they won't get remounted # unless they set their own keys are differently. # Switching back and forth between 1 and 2 implicitly # tests how components update when they aren't remounted. "chapter2": html.Div([ html.H1("Chapter 2", id="chapter2-header"), dcc.RadioItems( options=[{ "label": i, "value": i } for i in ["USA", "Canada"]], value="USA", id="chapter2-controls", ), html.Label(id="chapter2-label"), dcc.Graph(id="chapter2-graph"), ]), # Chapter 3 has a different layout and so the components # should get rewritten "chapter3": [ html.Div( html.Div([ html.H3("Chapter 3", id="chapter3-header"), html.Label(id="chapter3-label"), dcc.Graph(id="chapter3-graph"), dcc.RadioItems( options=[{ "label": i, "value": i } for i in ["Summer", "Winter"]], value="Winter", id="chapter3-controls", ), ])) ], # Chapter 4 doesn't have an object to recursively traverse "chapter4": "Just a string", } call_counts = { "body": Value("i", 0), "chapter1-graph": Value("i", 0), "chapter1-label": Value("i", 0), "chapter2-graph": Value("i", 0), "chapter2-label": Value("i", 0), "chapter3-graph": Value("i", 0), "chapter3-label": Value("i", 0), } @app.callback(Output("body", "children"), [Input("toc", "value")]) def display_chapter(toc_value): call_counts["body"].value += 1 return chapters[toc_value] app.config.suppress_callback_exceptions = True def generate_graph_callback(counterId): def callback(value): call_counts[counterId].value += 1 return { "data": [{ "x": ["Call Counter for: {}".format(counterId)], "y": [call_counts[counterId].value], "type": "bar", }], "layout": { "title": value, "width": 500, "height": 400, "margin": { "autoexpand": False }, }, } return callback def generate_label_callback(id_): def update_label(value): call_counts[id_].value += 1 return value return update_label for chapter in ["chapter1", "chapter2", "chapter3"]: app.callback( Output("{}-graph".format(chapter), "figure"), [Input("{}-controls".format(chapter), "value")], )(generate_graph_callback("{}-graph".format(chapter))) app.callback( Output("{}-label".format(chapter), "children"), [Input("{}-controls".format(chapter), "value")], )(generate_label_callback("{}-label".format(chapter))) dash_duo.start_server(app) def check_chapter(chapter): dash_duo.wait_for_element( "#{}-graph:not(.dash-graph--pending)".format(chapter)) for key in dash_duo.redux_state_paths["strs"]: assert dash_duo.find_elements( "#{}".format(key)), "each element should exist in the dom" value = (chapters[chapter][0]["{}-controls".format(chapter)].value if chapter == "chapter3" else chapters[chapter]["{}-controls".format(chapter)].value) # check the actual values dash_duo.wait_for_text_to_equal("#{}-label".format(chapter), value) wait.until( lambda: (dash_duo.driver.execute_script( 'return document.querySelector("' + "#{}-graph:not(.dash-graph--pending) .js-plotly-plot".format( chapter) + '").layout.title.text') == value), TIMEOUT, ) assert not dash_duo.redux_state_is_loading, "loadingMap is empty" def check_call_counts(chapters, count): for chapter in chapters: assert call_counts[chapter + "-graph"].value == count assert call_counts[chapter + "-label"].value == count wait.until(lambda: call_counts["body"].value == 1, TIMEOUT) wait.until(lambda: call_counts["chapter1-graph"].value == 1, TIMEOUT) wait.until(lambda: call_counts["chapter1-label"].value == 1, TIMEOUT) check_call_counts(("chapter2", "chapter3"), 0) assert dash_duo.redux_state_paths == EXPECTED_PATHS["chapter1"] check_chapter("chapter1") dash_duo.percy_snapshot(name="chapter-1") dash_duo.find_elements('input[type="radio"]')[1].click() # switch chapters wait.until(lambda: call_counts["body"].value == 2, TIMEOUT) wait.until(lambda: call_counts["chapter2-graph"].value == 1, TIMEOUT) wait.until(lambda: call_counts["chapter2-label"].value == 1, TIMEOUT) check_call_counts(("chapter1", ), 1) assert dash_duo.redux_state_paths == EXPECTED_PATHS["chapter2"] check_chapter("chapter2") dash_duo.percy_snapshot(name="chapter-2") # switch to 3 dash_duo.find_elements('input[type="radio"]')[2].click() wait.until(lambda: call_counts["body"].value == 3, TIMEOUT) wait.until(lambda: call_counts["chapter3-graph"].value == 1, TIMEOUT) wait.until(lambda: call_counts["chapter3-label"].value == 1, TIMEOUT) check_call_counts(("chapter2", "chapter1"), 1) assert dash_duo.redux_state_paths == EXPECTED_PATHS["chapter3"] check_chapter("chapter3") dash_duo.percy_snapshot(name="chapter-3") dash_duo.find_elements('input[type="radio"]')[3].click() # switch to 4 dash_duo.wait_for_text_to_equal("#body", "Just a string") dash_duo.percy_snapshot(name="chapter-4") paths = dash_duo.redux_state_paths assert paths["objs"] == {} for key in paths["strs"]: assert dash_duo.find_elements( "#{}".format(key)), "each element should exist in the dom" assert paths["strs"] == { "toc": ["props", "children", 0], "body": ["props", "children", 1], } dash_duo.find_elements('input[type="radio"]')[0].click() wait.until( lambda: dash_duo.redux_state_paths == EXPECTED_PATHS["chapter1"], TIMEOUT) check_chapter("chapter1") dash_duo.percy_snapshot(name="chapter-1-again")
import dash_bootstrap_components as dbc from dash import Input, Output, State, html offcanvas = html.Div([ dbc.Button("Open Offcanvas", id="open-offcanvas", n_clicks=0), dbc.Offcanvas( html.P("This is the content of the Offcanvas. " "Close it by clicking on the close button, or " "the backdrop."), id="offcanvas", title="Title", is_open=False, ), ]) @app.callback( Output("offcanvas", "is_open"), Input("open-offcanvas", "n_clicks"), [State("offcanvas", "is_open")], ) def toggle_offcanvas(n1, is_open): if n1: return not is_open return is_open
def update_realizations(app: Dash, get_uuid: Callable) -> None: @app.callback( Output( { "id": get_uuid("dialog"), "dialog_id": "realization-filter", "element": "apply", }, "disabled", ), Output( { "id": get_uuid("dialog"), "dialog_id": "realization-filter", "element": "apply", }, "style", ), Input( get_uuid("realization-store"), "data", ), Input( {"id": get_uuid("intersection-data"), "element": "realizations"}, "value", ), ) def _activate_realization_apply_btn( stored_reals: List, selected_reals: List ) -> Tuple[bool, Dict[str, str]]: if stored_reals is None or selected_reals is None: raise PreventUpdate if set(stored_reals) == set(selected_reals): return True, {"visibility": "hidden"} return False, {"visibility": "visible"} @app.callback( Output( get_uuid("realization-store"), "data", ), Input( { "id": get_uuid("dialog"), "dialog_id": "realization-filter", "element": "apply", }, "n_clicks", ), State( {"id": get_uuid("intersection-data"), "element": "realizations"}, "value", ), ) def _store_realizations(btn_click: Optional[int], selected_reals: List) -> List: if btn_click: return selected_reals raise PreventUpdate @app.callback( Output( {"id": get_uuid("intersection-data"), "element": "realizations"}, "value", ), Input( { "id": get_uuid("dialog"), "dialog_id": "realization-filter", "element": "clear", }, "n_clicks", ), Input( { "id": get_uuid("dialog"), "dialog_id": "realization-filter", "element": "all", }, "n_clicks", ), State( {"id": get_uuid("intersection-data"), "element": "realizations"}, "options", ), ) def _update_realization_list( clear_click: Optional[int], all_click: Optional[int], real_opts: List ) -> List: if clear_click is None and all_click is None: raise PreventUpdate ctx = callback_context.triggered if "clear" in ctx[0]["prop_id"]: return [] if "all" in ctx[0]["prop_id"]: return [opt["value"] for opt in real_opts] raise PreventUpdate
def callbacks(self, app): @app.callback( Output("control-panel", "data"), # Store Output("graph-passed", "figure"), Output("graph-duration", "figure"), Output("card-url", "children"), Output("ri-ttypes", "options"), Output("ri-cadences", "options"), Output("dd-tbeds", "options"), Output("ri-duts", "value"), Output("ri-ttypes", "value"), Output("ri-cadences", "value"), Output("dd-tbeds", "value"), Output("al-job", "children"), State("control-panel", "data"), # Store Input("ri-duts", "value"), Input("ri-ttypes", "value"), Input("ri-cadences", "value"), Input("dd-tbeds", "value"), Input("dpr-period", "start_date"), Input("dpr-period", "end_date"), Input("url", "href") # prevent_initial_call=True ) def _update_ctrl_panel(cp_data: dict, dut: str, ttype: str, cadence: str, tbed: str, start: str, end: str, href: str) -> tuple: """ """ ctrl_panel = self.ControlPanel(cp_data, self.default) start = self._get_date(start) end = self._get_date(end) # Parse the url: parsed_url = url_decode(href) if parsed_url: url_params = parsed_url["params"] else: url_params = None trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0] if trigger_id == "ri-duts": ttype_opts = self._generate_options(self._get_ttypes(dut)) ttype_val = ttype_opts[0]["value"] cad_opts = self._generate_options( self._get_cadences(dut, ttype_val)) cad_val = cad_opts[0]["value"] tbed_opts = self._generate_options( self._get_test_beds(dut, ttype_val, cad_val)) tbed_val = tbed_opts[0]["value"] ctrl_panel.set({ "ri-duts-value": dut, "ri-ttypes-options": ttype_opts, "ri-ttypes-value": ttype_val, "ri-cadences-options": cad_opts, "ri-cadences-value": cad_val, "dd-tbeds-options": tbed_opts, "dd-tbeds-value": tbed_val }) elif trigger_id == "ri-ttypes": cad_opts = self._generate_options( self._get_cadences(ctrl_panel.get("ri-duts-value"), ttype)) cad_val = cad_opts[0]["value"] tbed_opts = self._generate_options( self._get_test_beds(ctrl_panel.get("ri-duts-value"), ttype, cad_val)) tbed_val = tbed_opts[0]["value"] ctrl_panel.set({ "ri-ttypes-value": ttype, "ri-cadences-options": cad_opts, "ri-cadences-value": cad_val, "dd-tbeds-options": tbed_opts, "dd-tbeds-value": tbed_val }) elif trigger_id == "ri-cadences": tbed_opts = self._generate_options( self._get_test_beds(ctrl_panel.get("ri-duts-value"), ctrl_panel.get("ri-ttypes-value"), cadence)) tbed_val = tbed_opts[0]["value"] ctrl_panel.set({ "ri-cadences-value": cadence, "dd-tbeds-options": tbed_opts, "dd-tbeds-value": tbed_val }) elif trigger_id == "dd-tbeds": ctrl_panel.set({"dd-tbeds-value": tbed}) elif trigger_id == "dpr-period": pass elif trigger_id == "url": # TODO: Add verification if url_params: new_job = url_params.get("job", list())[0] new_start = url_params.get("start", list())[0] new_end = url_params.get("end", list())[0] if new_job and new_start and new_end: start = self._get_date(new_start) end = self._get_date(new_end) job_params = self._set_job_params(new_job) ctrl_panel = self.ControlPanel(None, job_params) else: ctrl_panel = self.ControlPanel(cp_data, self.default) job = self._get_job(ctrl_panel.get("ri-duts-value"), ctrl_panel.get("ri-ttypes-value"), ctrl_panel.get("ri-cadences-value"), ctrl_panel.get("dd-tbeds-value")) job = self._get_job(ctrl_panel.get("ri-duts-value"), ctrl_panel.get("ri-ttypes-value"), ctrl_panel.get("ri-cadences-value"), ctrl_panel.get("dd-tbeds-value")) ctrl_panel.set({"al-job-children": job}) fig_passed, fig_duration = graph_statistics( self.data, job, self.layout, start, end) if parsed_url: new_url = url_encode({ "scheme": parsed_url["scheme"], "netloc": parsed_url["netloc"], "path": parsed_url["path"], "params": { "job": job, "start": start, "end": end } }) else: new_url = str() ret_val = [ ctrl_panel.panel, fig_passed, fig_duration, [ # URL dcc.Clipboard(target_id="card-url", title="Copy URL", style={"display": "inline-block"}), new_url ] ] ret_val.extend(ctrl_panel.values()) return ret_val @app.callback( Output("download-data", "data"), State("control-panel", "data"), # Store State("dpr-period", "start_date"), State("dpr-period", "end_date"), Input("btn-download-data", "n_clicks"), prevent_initial_call=True) def _download_data(cp_data: dict, start: str, end: str, n_clicks: int): """ """ if not (n_clicks): raise PreventUpdate ctrl_panel = self.ControlPanel(cp_data, self.default) job = self._get_job(ctrl_panel.get("ri-duts-value"), ctrl_panel.get("ri-ttypes-value"), ctrl_panel.get("ri-cadences-value"), ctrl_panel.get("dd-tbeds-value")) start = datetime(int(start[0:4]), int(start[5:7]), int(start[8:10])) end = datetime(int(end[0:4]), int(end[5:7]), int(end[8:10])) data = select_data(self.data, job, start, end) data = data.drop(columns=[ "job", ]) return dcc.send_data_frame(data.T.to_csv, f"{job}-stats.csv") @app.callback(Output("row-metadata", "children"), Output("offcanvas-metadata", "is_open"), Input("graph-passed", "clickData"), Input("graph-duration", "clickData"), prevent_initial_call=True) def _show_metadata_from_graphs(passed_data: dict, duration_data: dict) -> tuple: """ """ if not (passed_data or duration_data): raise PreventUpdate metadata = no_update open_canvas = False title = "Job Statistics" trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0] if trigger_id == "graph-passed": graph_data = passed_data["points"][0].get("hovertext", "") elif trigger_id == "graph-duration": graph_data = duration_data["points"][0].get("text", "") if graph_data: metadata = [ dbc.Card( class_name="gy-2 p-0", children=[ dbc.CardHeader(children=[ dcc.Clipboard( target_id="metadata", title="Copy", style={"display": "inline-block"}), title ]), dbc.CardBody( id="metadata", class_name="p-0", children=[ dbc.ListGroup(children=[ dbc.ListGroupItem([ dbc.Badge(x.split(":")[0]), x.split(": ")[1] ]) for x in graph_data.split("<br>") ], flush=True), ]) ]) ] open_canvas = True return metadata, open_canvas
dbc.Row([ dbc.Col([html.Label('CandleStick Chart')], width=dict(size=4, offset=2)), dbc.Col([html.Label('OHLC Chart')], width=dict(size=4, offset=2)) ]), dbc.Row([ dbc.Col([dcc.Graph(id='candle', figure={}, style={'height': '80vh'})], width=6), dbc.Col([dcc.Graph(id='ohlc', figure={}, style={'height': '80vh'})], width=6) ]), ], fluid=True) @callback( Output(component_id='candle', component_property='figure'), Output(component_id='ohlc', component_property='figure'), Input(component_id='oil-volume', component_property='value') ) def build_graphs(chosen_volume): # represents that which is assigned to the component property of the Input dff = df[df.Volume > chosen_volume] print(dff.head()) fig_candle = go.Figure( go.Candlestick(x=dff['Date'], open=dff['Open'], high=dff['High'], low=dff['Low'], close=dff['Close'], text=dff['Volume']) ) fig_candle.update_layout(margin=dict(t=30, b=30)) # xaxis_rangeslider_visible=False,
import dash from dash import html, Input, Output, callback dash.register_page(__name__, id="page1") layout = html.Div([ html.Div("text for page1", id="text_page1"), html.Button("goto page2", id="btn1", n_clicks=0), ]) @callback(Output("url", "pathname"), Input("btn1", "n_clicks")) def update(n): if n > 0: return "/page2" return dash.no_update
def test_grva009_originals_maintained_for_responsive_override( mutate_fig, dash_dcc): # In #905 we made changes to prevent shapes from being lost. # This test makes sure that the overrides applied by the `responsive` # prop are "undone" when the `responsive` prop changes. app = Dash(__name__) graph = dcc.Graph( id="graph", figure={ "data": [{ "y": [1, 2] }], "layout": { "width": 300, "height": 250 } }, style={ "height": "400px", "width": "500px" }, ) responsive_size = [500, 400] fixed_size = [300, 250] app.layout = html.Div([ graph, html.Br(), html.Button(id="edit_figure", children="Edit figure"), html.Button(id="edit_responsive", children="Edit responsive"), html.Div(id="output", children=""), ]) if mutate_fig: # Modify the layout in place (which still has changes made by responsive) change_fig = """ figure.layout.title = {text: String(n_fig || 0)}; const new_figure = {...figure}; """ else: # Or create a new one each time change_fig = """ const new_figure = { data: [{y: [1, 2]}], layout: {width: 300, height: 250, title: {text: String(n_fig || 0)}} }; """ callback = (""" function clone_figure(n_fig, n_resp, figure) { """ + change_fig + """ let responsive = [true, false, 'auto'][(n_resp || 0) % 3]; return [new_figure, responsive, (n_fig || 0) + ' ' + responsive]; } """) app.clientside_callback( callback, Output("graph", "figure"), Output("graph", "responsive"), Output("output", "children"), Input("edit_figure", "n_clicks"), Input("edit_responsive", "n_clicks"), State("graph", "figure"), ) dash_dcc.start_server(app) edit_figure = dash_dcc.wait_for_element("#edit_figure") edit_responsive = dash_dcc.wait_for_element("#edit_responsive") def graph_dims(): return dash_dcc.driver.execute_script(""" const layout = document.querySelector('.js-plotly-plot')._fullLayout; return [layout.width, layout.height]; """) dash_dcc.wait_for_text_to_equal("#output", "0 true") dash_dcc.wait_for_text_to_equal(".gtitle", "0") assert graph_dims() == responsive_size edit_figure.click() dash_dcc.wait_for_text_to_equal("#output", "1 true") dash_dcc.wait_for_text_to_equal(".gtitle", "1") assert graph_dims() == responsive_size edit_responsive.click() dash_dcc.wait_for_text_to_equal("#output", "1 false") dash_dcc.wait_for_text_to_equal(".gtitle", "1") assert graph_dims() == fixed_size edit_figure.click() dash_dcc.wait_for_text_to_equal("#output", "2 false") dash_dcc.wait_for_text_to_equal(".gtitle", "2") assert graph_dims() == fixed_size edit_responsive.click() dash_dcc.wait_for_text_to_equal("#output", "2 auto") dash_dcc.wait_for_text_to_equal(".gtitle", "2") assert graph_dims() == fixed_size edit_figure.click() dash_dcc.wait_for_text_to_equal("#output", "3 auto") dash_dcc.wait_for_text_to_equal(".gtitle", "3") assert graph_dims() == fixed_size edit_responsive.click() dash_dcc.wait_for_text_to_equal("#output", "3 true") dash_dcc.wait_for_text_to_equal(".gtitle", "3") assert graph_dims() == responsive_size assert dash_dcc.get_logs() == []