def test_incs001_csp_hashes_inline_scripts(dash_duo, add_hashes, hash_algorithm, expectation): app = Dash(__name__) app.layout = html.Div([ dcc.Input(id="input_element", type="text"), html.Div(id="output_element") ]) app.clientside_callback( """ function(input) { return input; } """, Output("output_element", "children"), [Input("input_element", "value")], ) with expectation: csp = { "default-src": "'self'", "script-src": ["'self'"] + (app.csp_hashes(hash_algorithm) if add_hashes else []), } flask_talisman.Talisman(app.server, content_security_policy=csp, force_https=False) dash_duo.start_server(app) dash_duo.find_element("#input_element").send_keys("xyz") assert dash_duo.wait_for_element("#output_element").text == "xyz"
def test_clsd001_simple_clientside_serverside_callback(dash_duo): app = Dash(__name__, assets_folder="clientside_assets") app.layout = html.Div([ dcc.Input(id="input"), html.Div(id="output-clientside"), html.Div(id="output-serverside"), ]) @app.callback(Output("output-serverside", "children"), [Input("input", "value")]) def update_output(value): return 'Server says "{}"'.format(value) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="display"), Output("output-clientside", "children"), [Input("input", "value")], ) class DashView(BaseDashView): dash = app dash_duo.start_server(DashView) dash_duo.wait_for_text_to_equal("#output-serverside", 'Server says "None"') dash_duo.wait_for_text_to_equal("#output-clientside", 'Client says "undefined"') dash_duo.find_element("#input").send_keys("hello world") dash_duo.wait_for_text_to_equal("#output-serverside", 'Server says "hello world"') dash_duo.wait_for_text_to_equal("#output-clientside", 'Client says "hello world"')
def test_clsd006_no_update(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ dcc.Input(id="first", value=1), dcc.Input(id="second", value=1), dcc.Input(id="third", value=1) ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="add1_no_update_at_11"), [Output("second", "value"), Output("third", "value")], [Input("first", "value")], [State("second", "value"), State("third", "value")]) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#first", '1') dash_duo.wait_for_text_to_equal("#second", '2') dash_duo.wait_for_text_to_equal("#third", '2') dash_duo.find_element("#first").send_keys("1") dash_duo.wait_for_text_to_equal("#first", '11') dash_duo.wait_for_text_to_equal("#second", '2') dash_duo.wait_for_text_to_equal("#third", '3') dash_duo.find_element("#first").send_keys("1") dash_duo.wait_for_text_to_equal("#first", '111') dash_duo.wait_for_text_to_equal("#second", '3') dash_duo.wait_for_text_to_equal("#third", '4')
def test_clsd017_clientside_serverside_shared_input_with_promise(dash_duo): lock = Lock() lock.acquire() app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Div(id="input", children=["initial"]), html.Div(id="clientside-div"), html.Div(id="serverside-div"), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="non_delayed_promise"), Output("clientside-div", "children"), Input("input", "children"), ) @app.callback(Output("serverside-div", "children"), Input("input", "children")) def callback(value): with lock: return "serverside-" + value[0] + "-deferred" dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#clientside-div", "clientside-initial") lock.release() dash_duo.wait_for_text_to_equal("#serverside-div", "serverside-initial-deferred")
def setup_callbacks(app: Dash): @app.callback(Output("nav-list", "children"), [Input('url', 'href')], [State("report-query", "data")], prevent_initial_call=True) def update_nav_links(_, query_string): return generate_nav_links(query_string) # app.clientside_callback( # ClientsideFunction( # namespace="compliance", # function_name="download_pdf" # ), # Output("report-download", "data-download"), # [Input("report-download", "n_clicks")], # prevent_initial_call=True # ) app.clientside_callback( ClientsideFunction( namespace="compliance", function_name="download_png" ), Output("report-download-png", "data-download"), [Input("report-download-png", "n_clicks")], prevent_initial_call=True )
def test_clsd014_input_output_callback(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Div(id="input-text"), dcc.Input(id="input", type="number", value=0) ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="input_output_callback"), Output("input", "value"), Input("input", "value"), ) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="input_output_follower"), Output("input-text", "children"), Input("input", "value"), ) dash_duo.start_server(app) dash_duo.find_element("#input").send_keys("2") dash_duo.wait_for_text_to_equal("#input-text", "3") call_count = dash_duo.driver.execute_script("return window.callCount;") assert call_count == 2, "initial + changed once" assert dash_duo.get_logs() == []
def test_clsd016_serverside_clientside_shared_input_with_promise(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Div(id="input", children=["initial"]), html.Div(id="clientside-div"), html.Div(id="serverside-div"), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="delayed_promise"), Output("clientside-div", "children"), Input("input", "children"), ) @app.callback(Output("serverside-div", "children"), Input("input", "children")) def callback(value): return "serverside-" + value[0] dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#serverside-div", "serverside-initial") dash_duo.driver.execute_script("window.callbackDone('deferred')") dash_duo.wait_for_text_to_equal("#clientside-div", "clientside-initial-deferred")
def test_clsd008_clientside_inline_source(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div( [ dcc.Input(id="input"), html.Div(id="output-clientside"), html.Div(id="output-serverside"), ] ) @app.callback(Output("output-serverside", "children"), [Input("input", "value")]) def update_output(value): return 'Server says "{}"'.format(value) app.clientside_callback( """ function (value) { return 'Client says "' + value + '"'; } """, Output("output-clientside", "children"), [Input("input", "value")], ) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#output-serverside", 'Server says "None"') dash_duo.wait_for_text_to_equal("#output-clientside", 'Client says "undefined"') dash_duo.find_element("#input").send_keys("hello world") dash_duo.wait_for_text_to_equal("#output-serverside", 'Server says "hello world"') dash_duo.wait_for_text_to_equal("#output-clientside", 'Client says "hello world"')
def test_clsd011_clientside_callback_context_inputs_list(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Button("btn0", id="btn0"), html.Button("btn1:0", id={"btn1": 0}), html.Button("btn1:1", id={"btn1": 1}), html.Button("btn1:2", id={"btn1": 2}), html.Div(id="output-clientside", style={"font-family": "monospace"}), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="inputs_list_to_str"), Output("output-clientside", "children"), [Input("btn0", "n_clicks"), Input({"btn1": ALL}, "n_clicks")], ) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"btn0","property":"n_clicks"},' '[{"id":{"btn1":0},"property":"n_clicks"},' '{"id":{"btn1":1},"property":"n_clicks"},' '{"id":{"btn1":2},"property":"n_clicks"}]]'), ) dash_duo.find_element("#btn0").click() dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"btn0","property":"n_clicks","value":1},' '[{"id":{"btn1":0},"property":"n_clicks"},' '{"id":{"btn1":1},"property":"n_clicks"},' '{"id":{"btn1":2},"property":"n_clicks"}]]'), ) dash_duo.find_element("button[id*='btn1\":0']").click() dash_duo.find_element("button[id*='btn1\":0']").click() dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"btn0","property":"n_clicks","value":1},' '[{"id":{"btn1":0},"property":"n_clicks","value":2},' '{"id":{"btn1":1},"property":"n_clicks"},' '{"id":{"btn1":2},"property":"n_clicks"}]]'), ) dash_duo.find_element("button[id*='btn1\":2']").click() dash_duo.wait_for_text_to_equal( "#output-clientside", ('[{"id":"btn0","property":"n_clicks","value":1},' '[{"id":{"btn1":0},"property":"n_clicks","value":2},' '{"id":{"btn1":1},"property":"n_clicks"},' '{"id":{"btn1":2},"property":"n_clicks","value":1}]]'), )
def test_grva008_shapes_not_lost(dash_dcc): # See issue #879 and pr #905 app = Dash(__name__) fig = {"data": [], "layout": {"dragmode": "drawrect"}} graph = dcc.Graph(id="graph", figure=fig, style={"height": "400px"}) app.layout = html.Div( [ graph, html.Br(), html.Button(id="button", children="Clone figure"), html.Div(id="output", children=""), ] ) app.clientside_callback( """ function clone_figure(_, figure) { const new_figure = {...figure}; const shapes = new_figure.layout.shapes || []; return [new_figure, shapes.length]; } """, Output("graph", "figure"), Output("output", "children"), Input("button", "n_clicks"), State("graph", "figure"), ) dash_dcc.start_server(app) button = dash_dcc.wait_for_element("#button") dash_dcc.wait_for_text_to_equal("#output", "0") # Draw a shape dash_dcc.click_and_hold_at_coord_fractions("#graph", 0.25, 0.25) dash_dcc.move_to_coord_fractions("#graph", 0.35, 0.75) dash_dcc.release() # Click to trigger an update of the output, the shape should survive dash_dcc.wait_for_text_to_equal("#output", "0") button.click() dash_dcc.wait_for_text_to_equal("#output", "1") # Draw another shape dash_dcc.click_and_hold_at_coord_fractions("#graph", 0.75, 0.25) dash_dcc.move_to_coord_fractions("#graph", 0.85, 0.75) dash_dcc.release() # Click to trigger an update of the output, the shape should survive dash_dcc.wait_for_text_to_equal("#output", "1") button.click() dash_dcc.wait_for_text_to_equal("#output", "2") assert dash_dcc.get_logs() == []
def test_clsd004_clientside_multiple_outputs(dash_duo): app = Dash(__name__, assets_folder="clientside_assets") app.layout = html.Div([ dcc.Input(id="input", value=1), dcc.Input(id="output-1"), dcc.Input(id="output-2"), dcc.Input(id="output-3"), dcc.Input(id="output-4"), ]) app.clientside_callback( ClientsideFunction("clientside", "add_to_four_outputs"), [ Output("output-1", "value"), Output("output-2", "value"), Output("output-3", "value"), Output("output-4", "value"), ], [Input("input", "value")], ) class DashView(BaseDashView): dash = app dash_duo.start_server(DashView) for selector, expected in [ ["#input", "1"], ["#output-1", "2"], ["#output-2", "3"], ["#output-3", "4"], ["#output-4", "5"], ]: dash_duo.wait_for_text_to_equal(selector, expected) dash_duo.wait_for_element("#input").send_keys("1") for selector, expected in [ ["#input", "11"], ["#output-1", "12"], ["#output-2", "13"], ["#output-3", "14"], ["#output-4", "15"], ]: dash_duo.wait_for_text_to_equal(selector, expected)
def test_clsd006_PreventUpdate(dash_duo): app = Dash(__name__, assets_folder="clientside_assets") app.layout = html.Div([ dcc.Input(id="first", value=1), dcc.Input(id="second", value=1), dcc.Input(id="third", value=1), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="add1_prevent_at_11"), Output("second", "value"), [Input("first", "value")], [State("second", "value")], ) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="add1_prevent_at_11"), Output("third", "value"), [Input("second", "value")], [State("third", "value")], ) class DashView(BaseDashView): dash = app dash_duo.start_server(DashView) dash_duo.wait_for_text_to_equal("#first", "1") dash_duo.wait_for_text_to_equal("#second", "2") dash_duo.wait_for_text_to_equal("#third", "2") dash_duo.find_element("#first").send_keys("1") dash_duo.wait_for_text_to_equal("#first", "11") dash_duo.wait_for_text_to_equal("#second", "2") dash_duo.wait_for_text_to_equal("#third", "2") dash_duo.find_element("#first").send_keys("1") dash_duo.wait_for_text_to_equal("#first", "111") dash_duo.wait_for_text_to_equal("#second", "3") dash_duo.wait_for_text_to_equal("#third", "3")
def test_clsd009_clientside_callback_context_triggered(dash_duo): app = Dash(__name__, assets_folder="clientside_assets") app.layout = html.Div([ html.Button("btn0", id="btn0"), html.Button("btn1:0", id={"btn1": 0}), html.Button("btn1:1", id={"btn1": 1}), html.Button("btn1:2", id={"btn1": 2}), html.Div(id="output-clientside", style={"font-family": "monospace"}), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="triggered_to_str"), Output("output-clientside", "children"), [Input("btn0", "n_clicks"), Input({"btn1": ALL}, "n_clicks")], ) class DashView(BaseDashView): dash = app dash_duo.start_server(DashView) dash_duo.wait_for_text_to_equal("#output-clientside", "") dash_duo.find_element("#btn0").click() dash_duo.wait_for_text_to_equal( "#output-clientside", "btn0.n_clicks = 1", ) dash_duo.find_element("button[id*='btn1\":0']").click() dash_duo.find_element("button[id*='btn1\":0']").click() dash_duo.wait_for_text_to_equal("#output-clientside", '{"btn1":0}.n_clicks = 2') dash_duo.find_element("button[id*='btn1\":2']").click() dash_duo.wait_for_text_to_equal( "#output-clientside", '{"btn1":2}.n_clicks = 1', )
def apply(cls, app: Dash) -> None: for cb in cls.REGISTRY: try: if isinstance(cb, deferred_callback): LOG.info(f"Applying deferred callback {cb.f.__name__}") app.callback(*cb.args, **cb.kwargs)(cb.f) else: LOG.info( f"Applying deferred clientside callback {cb.name}") app.clientside_callback(*cb.args, **cb.kwargs) except Exception as e: LOG.error(f"Callback {type(cb)=}, {cb=}, " f"{getattr(cb, 'name', None)=}" f"{getattr(cb, 'f', None)=}" f"failed") LOG.error(e) continue
def test_rdls004_update_title_chained_callbacks(dash_duo, update_title): initial_title = "Initial Title" app = Dash("Dash", title=initial_title, update_title=update_title) lock = Lock() app.layout = html.Div(children=[ html.Button(id="page-title", n_clicks=0, children="Page Title"), html.Div(id="page-output"), html.Div(id="final-output"), ]) app.clientside_callback( """ function(n_clicks) { if (n_clicks > 0) { document.title = 'Page ' + n_clicks; } return n_clicks; } """, Output("page-output", "children"), [Input("page-title", "n_clicks")], ) @app.callback(Output("final-output", "children"), [Input("page-output", "children")]) def update(n): with lock: return n # check for original title after loading dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#final-output", "0") until(lambda: dash_duo.driver.title == initial_title, timeout=1) with lock: dash_duo.find_element("#page-title").click() # check for update-title while processing the serverside callback if update_title: until(lambda: dash_duo.driver.title == update_title, timeout=1) else: until(lambda: dash_duo.driver.title == "Page 1", timeout=1) dash_duo.wait_for_text_to_equal("#final-output", "1") until(lambda: dash_duo.driver.title == "Page 1", timeout=1)
def test_clsd003_clientside_exceptions_halt_subsequent_updates(dash_duo): app = Dash(__name__, assets_folder="clientside_assets") app.layout = html.Div([ dcc.Input(id="first", value=1), dcc.Input(id="second"), dcc.Input(id="third") ]) app.clientside_callback( ClientsideFunction("clientside", "add1_break_at_11"), Output("second", "value"), [Input("first", "value")], ) app.clientside_callback( ClientsideFunction("clientside", "add1_break_at_11"), Output("third", "value"), [Input("second", "value")], ) class DashView(BaseDashView): dash = app dash_duo.start_server(DashView) test_cases = [["#first", "1"], ["#second", "2"], ["#third", "3"]] for selector, expected in test_cases: dash_duo.wait_for_text_to_equal(selector, expected) first_input = dash_duo.wait_for_element("#first") first_input.send_keys("1") # clientside code will prevent the update from occurring test_cases = [["#first", "11"], ["#second", "2"], ["#third", "3"]] for selector, expected in test_cases: dash_duo.wait_for_text_to_equal(selector, expected) first_input.send_keys("1") # the previous clientside code error should not be fatal: # subsequent updates should still be able to occur test_cases = [["#first", "111"], ["#second", "112"], ["#third", "113"]] for selector, expected in test_cases: dash_duo.wait_for_text_to_equal(selector, expected)
def test_clsd018_clientside_inline_async_function(dash_duo): app = Dash(__name__) app.layout = html.Div([ html.Div(id="input", children=["initial"]), html.Div(id="output-div"), ]) app.clientside_callback( """ async function(input) { return input + "-inline"; } """, Output("output-div", "children"), Input("input", "children"), ) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#output-div", "initial-inline")
def test_clsd005_clientside_fails_when_returning_a_promise(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Div(id="input", children="hello"), html.Div(id="side-effect"), html.Div(id="output", children="output"), ]) app.clientside_callback( ClientsideFunction("clientside", "side_effect_and_return_a_promise"), Output("output", "children"), [Input("input", "children")], ) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#input", "hello") dash_duo.wait_for_text_to_equal("#side-effect", "side effect") dash_duo.wait_for_text_to_equal("#output", "output")
def test_clsd019_clientside_inline_promise(dash_duo): app = Dash(__name__) app.layout = html.Div([ html.Div(id="input", children=["initial"]), html.Div(id="output-div"), ]) app.clientside_callback( """ function(inputValue) { return new Promise(function (resolve) { resolve(inputValue + "-inline"); }); } """, Output("output-div", "children"), Input("input", "children"), ) dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#output-div", "initial-inline")
def setup_callbacks(app: dash.Dash, cache: cache_int.CacheInterface) -> None: create_upload_callback(app, "compliance-report", cache) create_upload_callback(app, "training-report", cache) create_button_callback(app, "button", "compliance-report", "training-report", cache) # Show a waiting spinner on clicking the button app.clientside_callback( ClientsideFunction( namespace="compliance", function_name="new_dashboard_spinner" ), Output("button", "data-popup"), [Input("button", "n_clicks")] ) # If components are missing, re-hide the spinner app.clientside_callback( ClientsideFunction( namespace="compliance", function_name="new_dashboard_spinner_hide" ), Output("button", "data-popup-hide"), [Input("button", "children")] )
def test_clsd015_clientside_chained_callbacks_returning_promise(dash_duo): app = Dash(__name__, assets_folder="assets") app.layout = html.Div([ html.Div(id="input", children=["initial"]), html.Div(id="div-1"), html.Div(id="div-2"), ]) app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="chained_promise"), Output("div-1", "children"), Input("input", "children"), ) @app.callback(Output("div-2", "children"), Input("div-1", "children")) def callback(value): return value + "-twice" dash_duo.start_server(app) dash_duo.wait_for_text_to_equal("#div-1", "initial-chained") dash_duo.wait_for_text_to_equal("#div-2", "initial-chained-twice")
def update_figure_clientside(app: Dash, get_uuid: Callable) -> None: @app.callback( Output( { "id": get_uuid("clientside"), "plotly_attribute": "update_layout" }, "data"), Input({ "id": get_uuid("plotly_layout"), "layout_attribute": ALL }, "value"), Input(get_uuid("plotly_uirevision"), "value"), ) def _update_layout(layout_attributes: Optional[List], uirevision: str) -> Dict: """Store plotly layout options from user selections in a dcc.Store""" if layout_attributes is None: return {} layout = {} for ctx in callback_context.inputs_list[0]: layout[ctx["id"]["layout_attribute"]] = ctx.get("value", None) layout["uirevision"] = str(uirevision) if uirevision else None return layout @app.callback( Output( { "id": get_uuid("clientside"), "plotly_attribute": "plotly_layout" }, "data"), Input( { "id": get_uuid("clientside"), "plotly_attribute": "initial_layout" }, "data"), Input( { "id": get_uuid("clientside"), "plotly_attribute": "update_layout" }, "data"), ) def _update_plot_layout(initial_layout: dict, update_layout: dict) -> Dict: if initial_layout is None: raise PreventUpdate fig = Figure({"layout": initial_layout}) if update_layout is not None: fig.update_layout(update_layout) return fig["layout"] app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="set_dcc_figure"), Output(get_uuid("graph"), "figure"), Input( { "id": get_uuid("clientside"), "plotly_attribute": "plotly_layout" }, "data"), Input({ "id": get_uuid("clientside"), "plotly_attribute": "plotly_data" }, "data"), )
def test_clsd012_clientside_callback_context_states(dash_duo): app = Dash(__name__, assets_folder="clientside_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_to_str"), Output("output-clientside", "children"), [Input("in0", "n_submit"), Input({"in1": ALL}, "n_submit")], [State("in0", "value"), State({"in1": ALL}, "value")], ) class DashView(BaseDashView): dash = app dash_duo.start_server(DashView) dash_duo.wait_for_text_to_equal( "#output-clientside", ("in0.value = null, " '{"in1":0}.value = null, ' '{"in1":1}.value = null, ' '{"in1":2}.value = null'), ) dash_duo.find_element("#in0").send_keys("test 0" + Keys.RETURN) dash_duo.wait_for_text_to_equal( "#output-clientside", ("in0.value = test 0, " '{"in1":0}.value = null, ' '{"in1":1}.value = null, ' '{"in1":2}.value = null'), ) dash_duo.find_element("input[id*='in1\":0']").send_keys("test 1" + Keys.RETURN) dash_duo.wait_for_text_to_equal( "#output-clientside", ("in0.value = test 0, " '{"in1":0}.value = test 1, ' '{"in1":1}.value = null, ' '{"in1":2}.value = null'), ) dash_duo.find_element("input[id*='in1\":2']").send_keys("test 2" + Keys.RETURN) dash_duo.wait_for_text_to_equal( "#output-clientside", ("in0.value = test 0, " '{"in1":0}.value = test 1, ' '{"in1":1}.value = null, ' '{"in1":2}.value = test 2'), )
def update_intersection( app: Dash, get_uuid: Callable, surface_set_models: Dict[str, SurfaceSetModel], well_set_model: WellSetModel, color_picker: ColorPicker, zonelog: Optional[str] = None, ) -> None: @app.callback( Output(get_uuid("intersection-graph-data"), "data"), Input(get_uuid("apply-intersection-data-selections"), "n_clicks"), Input( { "id": get_uuid("intersection-data"), "element": "source" }, "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": "well" }, "value"), Input(get_uuid("realization-store"), "data"), State( { "id": get_uuid("intersection-data"), "element": "surface_attribute" }, "value", ), State({ "id": get_uuid("intersection-data"), "element": "surface_names" }, "value"), State( { "id": get_uuid("intersection-data"), "element": "calculation" }, "value", ), State({ "id": get_uuid("intersection-data"), "element": "ensembles" }, "value"), State({ "id": get_uuid("intersection-data"), "element": "resolution" }, "value"), State({ "id": get_uuid("intersection-data"), "element": "extension" }, "value"), State(color_picker.color_store_id, "data"), ) # pylint: disable=too-many-arguments: disable=too-many-branches, too-many-locals def _store_intersection_traces( _apply_click: Optional[int], intersection_source: str, polyline: Optional[List], xline: Optional[List], yline: Optional[List], wellname: str, realizations: List[int], surfaceattribute: str, surfacenames: List[str], statistics: List[str], ensembles: str, resolution: float, extension: int, color_list: List[str], ) -> List: """Generate plotly traces for intersection figure and store clientside""" # TODO(Sigurd) Can we prohibit clearing of the sampling and extension input # fields (dcc.Input) in the client? Until we can, we must guard against sampling # and extension being None. This happens when the user clears the input field and we # have not yet found a solution that prohibits the input field from being cleared. # The situation can be slightly remedied by setting required=True which will highlight # the missing value with a red rectangle. if any(val is None for val in [resolution, extension]): raise PreventUpdate traces = [] if intersection_source == "polyline": if polyline is None: return [] fence_spec = get_fencespec_from_polyline(polyline, distance=resolution, atleast=5, nextend=extension / resolution) elif intersection_source == "xline": if xline is None: return [] fence_spec = get_fencespec_from_polyline(xline, distance=resolution, atleast=5, nextend=extension / resolution) elif intersection_source == "yline": if yline is None: return [] fence_spec = get_fencespec_from_polyline(yline, distance=resolution, atleast=5, nextend=extension / resolution) else: fence_spec = well_set_model.get_fence( well_name=wellname, distance=resolution, atleast=5, nextend=extension / resolution, ) realizations = [int(real) for real in realizations] for ensemble in ensembles: surfset = surface_set_models[ensemble] for surfacename in surfacenames: color = color_picker.get_color( color_list=color_list, filter_query={ "surfacename": surfacename, "ensemble": ensemble, }, ) showlegend = True if statistics is not None: for stat in ["Mean", "Min", "Max"]: if stat in statistics: trace = get_plotly_trace_statistical_surface( surfaceset=surfset, fence_spec=fence_spec, calculation=stat, legendname=f"{surfacename}({ensemble})", name=surfacename, attribute=surfaceattribute, realizations=realizations, showlegend=showlegend, color=color, ) traces.append(trace) showlegend = False if "Uncertainty envelope" in statistics: envelope_traces = get_plotly_traces_uncertainty_envelope( surfaceset=surfset, fence_spec=fence_spec, legendname=f"{surfacename}({ensemble})", name=surfacename, attribute=surfaceattribute, realizations=realizations, showlegend=showlegend, color=color, ) traces.extend(envelope_traces) showlegend = False if "Realizations" in statistics: for real in realizations: trace = get_plotly_trace_realization_surface( surfaceset=surfset, fence_spec=fence_spec, legendname=f"{surfacename}({ensemble})", name=surfacename, attribute=surfaceattribute, realization=real, color=color, showlegend=showlegend, ) traces.append(trace) showlegend = False if intersection_source == "well": well = well_set_model.get_well(wellname) traces.append(get_plotly_trace_well_trajectory(well)) if well.zonelogname is not None: traces.extend(get_plotly_zonelog_trace(well, zonelog)) return traces @app.callback( Output(get_uuid("intersection-graph-layout"), "data"), Input(get_uuid("intersection-graph-data"), "data"), Input(get_uuid("initial-intersection-graph-layout"), "data"), Input( { "id": get_uuid("intersection-data"), "element": "source" }, "value", ), Input( { "id": get_uuid("intersection-data"), "settings": "zrange_locks" }, "value", ), Input( { "id": get_uuid("intersection-data"), "settings": "zrange_min" }, "value", ), Input( { "id": get_uuid("intersection-data"), "settings": "zrange_max" }, "value", ), Input( { "id": get_uuid("intersection-data"), "settings": "ui_options" }, "value", ), State(get_uuid("leaflet-map1"), "polyline_points"), State({ "id": get_uuid("intersection-data"), "element": "well" }, "value"), ) # pylint: disable=too-many-arguments, too-many-branches def _store_intersection_layout( data: List, initial_layout: Optional[dict], intersection_source: str, zrange_locks: str, zmin: Optional[float], zmax: Optional[float], ui_options: List[str], polyline: Optional[List], wellname: str, ) -> Dict: """Store intersection layout configuration clientside""" ctx = callback_context.triggered[0] if "ui_options" in ctx["prop_id"]: raise PreventUpdate # Set default layout layout: Dict = { "hovermode": "closest", "yaxis": { "autorange": "reversed", "showgrid": False, "zeroline": False, "title": "True vertical depth", }, "xaxis": { "showgrid": False, "zeroline": False, "title": "Lateral resolution", }, "plot_bgcolor": "rgba(0, 0, 0, 0)", "paper_bgcolor": "rgba(0, 0, 0, 0)", } # Update title to reflect source of cross-section calculation annotation_title = ["A", "A'"] if intersection_source in ["polyline", "xline", "yline"]: layout.update({ "title": f"Intersection along {intersection_source} shown in Surface A" }) layout.get("xaxis", {}).update({"autorange": True}) annotation_title = ["B", "B'"] if intersection_source == "well": layout["title"] = f"Intersection along well: {wellname}" # Set A-B annotations on plot layout["annotations"] = [ { "x": 0, "y": 1, "xref": "paper", "yref": "paper", "text": f"<b>{annotation_title[0]}</b>", "font": { "size": 40 }, "showarrow": False, }, { "x": 1, "y": 1, "xref": "paper", "yref": "paper", "text": f"<b>{annotation_title[1]}</b>", "font": { "size": 40 }, "showarrow": False, }, ] # Update layout with any values provided from yaml configuration if initial_layout is not None: layout.update(initial_layout) # Return emptly plot layout if surface is source but no polyline is drawn if intersection_source == "polyline" and polyline is None: layout.update({ "title": "Draw a random line from the toolbar on Surface A", }) return layout # Add any interactivily set range options if ui_options: if "uirevision" in ui_options: layout.update({"uirevision": "keep"}) user_range = [] if not (zmax is None and zmin is None): if "lock" in zrange_locks: if zmax is None: zmax = max( max(x for x in item["y"] if x is not None) for item in data) if zmin is None: zmin = min( min(x for x in item["y"] if x is not None) for item in data) user_range = [zmax, zmin] if "truncate" in zrange_locks: zmin_data = min( min(x for x in item["y"] if x is not None) for item in data) zmax_data = max( max(x for x in item["y"] if x is not None) for item in data) zmax = zmax if zmax is not None else zmax_data zmin = zmin if zmin is not None else zmin_data user_range = [min(zmax, zmax_data), max(zmin, zmin_data)] # Set y-axis range from depth range input if specified if user_range: layout.get("yaxis", {}).update({"autorange": False}) layout.get("yaxis", {}).update(range=user_range) # Else autocalculate range if not intersecting a well elif intersection_source != "well": if "range" in layout.get("yaxis", {}): del layout["yaxis"]["range"] layout.get("yaxis", {}).update({"autorange": "reversed"}) # Remove xaxis zero line layout.get("xaxis", {}).update({"zeroline": False, "showline": False}) return layout # Store intersection data and layout to the plotly figure # Done clientside for performance app.clientside_callback( ClientsideFunction(namespace="clientside", function_name="set_dcc_figure"), Output(get_uuid("intersection-graph"), "figure"), Input(get_uuid("intersection-graph-layout"), "data"), State(get_uuid("intersection-graph-data"), "data"), ) @app.callback( Output( { "id": get_uuid("intersection-data"), "settings": "zrange_min" }, "max", ), Output( { "id": get_uuid("intersection-data"), "settings": "zrange_max" }, "min", ), Input( { "id": get_uuid("intersection-data"), "settings": "zrange_min" }, "value", ), Input( { "id": get_uuid("intersection-data"), "settings": "zrange_max" }, "value", ), ) def _set_min_max_for_range_input( zmin: Optional[float], zmax: Optional[float], ) -> Tuple[Optional[float], Optional[float]]: ctx = callback_context.triggered[0] if ctx["prop_id"] == ".": raise PreventUpdate return zmax, zmin @app.callback( Output(get_uuid("apply-intersection-data-selections"), "style"), Output( { "id": get_uuid("intersection-data"), "element": "stored_manual_update_options", }, "data", ), Input(get_uuid("apply-intersection-data-selections"), "n_clicks"), Input( { "id": get_uuid("intersection-data"), "element": "surface_attribute" }, "value", ), Input({ "id": get_uuid("intersection-data"), "element": "surface_names" }, "value"), Input( { "id": get_uuid("intersection-data"), "element": "calculation" }, "value", ), Input({ "id": get_uuid("intersection-data"), "element": "ensembles" }, "value"), Input({ "id": get_uuid("intersection-data"), "element": "resolution" }, "value"), Input({ "id": get_uuid("intersection-data"), "element": "extension" }, "value"), Input(color_picker.color_store_id, "data"), State( { "id": get_uuid("intersection-data"), "element": "stored_manual_update_options", }, "data", ), ) def _update_apply_button( _apply_click: Optional[int], surfaceattribute: str, surfacenames: List[str], statistics: List[str], ensembles: str, resolution: float, extension: int, color_list: List[str], previous_settings: Dict, ) -> Tuple[Dict, Dict]: ctx = callback_context.triggered[0] new_settings = { "surface_attribute": surfaceattribute, "surface_names": surfacenames, "calculation": statistics, "ensembles": ensembles, "resolution": resolution, "extension": extension, "colors": color_list, } # store selected settings if initial callback or apply button is pressed if ("apply-intersection-data-selections" in ctx["prop_id"] or ctx["prop_id"] == "."): return {"background-color": "#E8E8E8"}, new_settings element = ("colors" if "colorpicker" in ctx["prop_id"] else json.loads( ctx["prop_id"].replace(".value", "")).get("element")) if new_settings[element] != previous_settings[element]: return { "background-color": "#7393B3", "color": "#fff" }, previous_settings return {"background-color": "#E8E8E8"}, previous_settings
def set_callbacks(self, app: Dash) -> None: @app.callback( Output(self.ids("label"), "disabled"), Input(self.ids("plot-options"), "value"), ) def _disable_label(plot_options: List) -> bool: return "Show realization points" in plot_options @app.callback( Output(self.ids("graph-wrapper"), "style"), Output(self.ids("table-wrapper"), "style"), Input(self.ids("plot-or-table"), "value"), State(self.ids("graph-wrapper"), "style"), State(self.ids("table-wrapper"), "style"), ) def _set_visualization( viz_type: str, graph_style: dict, table_style: dict ) -> Tuple[Dict[str, str], Dict[str, str]]: if viz_type == "bars": graph_style.update({"display": "inline"}) table_style.update({"display": "none"}) if viz_type == "table": graph_style.update({"display": "none"}) table_style.update({"display": "inline"}) return graph_style, table_style app.clientside_callback( ClientsideFunction( namespace="clientside", function_name="get_client_height" ), Output(self.ids("client-height-pixels"), "data"), Input(self.ids("plot-options"), "value"), ) @app.callback( Output(self.ids("tornado-graph"), "figure"), Output(self.ids("tornado-table"), "data"), Output(self.ids("tornado-table"), "columns"), Output(self.ids("high-low-storage"), "data"), Input(self.ids("reference"), "value"), Input(self.ids("scale"), "value"), Input(self.ids("plot-options"), "value"), Input(self.ids("label"), "value"), Input(self.ids("storage"), "data"), Input(self.ids("sens_filter"), "value"), State(self.ids("client-height-pixels"), "data"), ) def _calc_tornado( reference: str, scale: str, plot_options: List, label_option: str, data: Union[str, bytes, bytearray], sens_filter: List[str], client_height: Optional[int], ) -> Tuple[dict, dict]: if not data: raise PreventUpdate plot_options = plot_options if plot_options else [] data = json.loads(data) if not isinstance(data, dict): raise PreventUpdate values = pd.DataFrame(data["data"], columns=["REAL", "VALUE"]) realizations = self.realizations.loc[ self.realizations["ENSEMBLE"] == data["ENSEMBLE"] ] design_and_responses = pd.merge(values, realizations, on="REAL") if sens_filter is not None: if reference not in sens_filter: sens_filter.append(reference) design_and_responses = design_and_responses.loc[ design_and_responses["SENSNAME"].isin(sens_filter) ] tornado_data = TornadoData( dframe=design_and_responses, response_name=data.get("response_name"), reference=reference, scale="Percentage" if scale == "Relative value (%)" else "Absolute", cutbyref="Remove sensitivites with no impact" in plot_options, ) figure_height = ( client_height * 0.59 if "Fit all bars in figure" in plot_options and client_height is not None else max(100 * len(tornado_data.tornadotable["sensname"].unique()), 200) ) tornado_figure = TornadoBarChart( tornado_data=tornado_data, plotly_theme=self.plotly_theme, figure_height=figure_height, label_options=label_option, number_format=data.get("number_format", ""), unit=data.get("unit", ""), spaced=data.get("spaced", True), locked_si_prefix=data.get("locked_si_prefix", None), use_true_base=scale == "True value", show_realization_points="Show realization points" in plot_options, color_by_sensitivity="Color bars by sensitivity" in plot_options, ) tornado_table = TornadoTable(tornado_data=tornado_data) return ( tornado_figure.figure, tornado_table.as_plotly_table, tornado_table.columns, tornado_data.low_high_realizations_list, ) if self.allow_click: @app.callback( Output(self.ids("click-store"), "data"), [ Input(self.ids("tornado-graph"), "clickData"), Input(self.ids("reset"), "n_clicks"), State(self.ids("high-low-storage"), "data"), ], ) def _save_click_data( data: dict, nclicks: Optional[int], sens_reals: dict ) -> str: if ( callback_context.triggered is None or sens_reals is None or data is None ): raise PreventUpdate ctx = callback_context.triggered[0]["prop_id"].split(".")[0] if ctx == self.ids("reset") and nclicks: return json.dumps( { "real_low": [], "real_high": [], "sens_name": None, } ) sensname = data["points"][0]["y"] real_high = sens_reals[sensname]["real_high"] real_low = sens_reals[sensname]["real_low"] return json.dumps( { "real_low": real_low, "real_high": real_high, "sens_name": sensname, } )
html.Div(id='wordcloud-div', children=[dcc.Loading(children=[ html.Img(id="wordcloud-img", style={ "max-width": "80%", "height": "auto", "display": "block", "margin-left": "auto", "margin-right": "auto"})])]), ] ), html.Div(children=[html.P("Created with ❤️ and Python")], className="footer", style={"text-align": "center", "height": "50px"}) ]) # Get screen resolution on client side app.clientside_callback( """ function(url) { return "".concat(window.innerWidth, "x", window.innerHeight); } """, Output('size', 'children'), [Input('url', 'href')] ) # Update Upload Text @app.callback( Output(component_id='upload-text', component_property='children'), [Input('upload-text', 'filename'),], [State('upload-text', 'last_modified')]) def update_upload(file_name, last_modified): if file_name is None: return html.Div([ html.A('Upload'), ' Your Own Book'
def init_graphing(server): external_stylesheets = [themes.BOOTSTRAP] app = Dash('__main__', server=server, url_base_pathname=url_base, assets_folder='static', external_stylesheets=external_stylesheets) data = pd.read_csv('static/Sun.txt') df = data[data['ACCEPT'] == True] rv_figure = scatter(df, x="MJD", y="V") rv_figure.update_layout(clickmode='event') app.layout = html.Div([ dcc.Loading([ dcc.Graph(id='rv-plot', className="pt-5", config={ "displaylogo": False, 'modeBarButtonsToRemove': ['pan2d', 'lasso2d', 'autoscale'] }), dcc.Input(id="x1", type="number", placeholder="", debounce=True), dcc.Input(id="x2", type="number", placeholder="", debounce=True), html.Br(), dcc.Input(id="y1", type="number", placeholder="", debounce=True), dcc.Input(id="y2", type="number", placeholder="", debounce=True), ]), html.Div(id='data', children=df.to_json(), className='d-none'), html.Div([ dcc.Markdown(""" **Click Data** Click on points in the graph. """), html.Pre(id='click-data'), ]), html.Div([], id='spec-container'), ]) app.clientside_callback( output=Output('click-data', 'children'), inputs=[Input('rv-plot', 'clickData'), State('data', 'children')], clientside_function=ClientsideFunction(namespace='graphing', function_name='clickData')) app.clientside_callback( output=Output('rv-plot', 'figure'), inputs=[ Input('x1', 'value'), Input('x2', 'value'), Input('y1', 'value'), Input('y2', 'value'), State('data', 'children') ], clientside_function=ClientsideFunction(namespace='graphing', function_name='zoomfunc')) @app.callback(Output('spec-container', 'children'), Input('click-data', 'children')) def getGraph(children): if (children == None): raise PreventUpdate data = mongo.db.onespectrum.find({"FILENAME": "Sun_200911.1062"}, { "_id": 0, "# WAVE": 1, "FLUX": 1 }) data_dict = {'wave': [], 'flux': []} count = 0 realCount = 0 print(data[0]["# WAVE"]) for x in data: count += 1 if (x["# WAVE"] != '' and count % 10 == 0): data_dict['wave'].append(float(x["# WAVE"])) data_dict['flux'].append(float(x["FLUX"])) realCount += 1 print("Get") spec = line(data_dict, x="wave", y="flux", render_mode="webgl") print(realCount) return [ children, dcc.Graph(id='spec-plot', figure=rv_figure, className="pt-5") ] with server.test_client() as client: client.get('/') app.index_string = render_template('data_page.html')
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
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 init_graphing(server): external_stylesheets = [themes.BOOTSTRAP] app = Dash('__main__', server=server, url_base_pathname=url_base, assets_folder='static', external_stylesheets=external_stylesheets) data = pd.read_csv('static/Sun.txt') df = data[data['ACCEPT'] == True] rv_figure = scatter(df, x="MJD", y="V") rv_figure.update_layout(clickmode='event') app.layout = html.Div([ dcc.Loading([ dcc.Graph(id='rv-plot', className="pt-5", config={ "displaylogo": False, 'modeBarButtonsToRemove': ['pan2d', 'lasso2d', 'autoscale'] }), dcc.Input(id="x1", type="number", placeholder="", debounce=True), dcc.Input(id="x2", type="number", placeholder="", debounce=True), html.Br(), dcc.Input(id="y1", type="number", placeholder="", debounce=True), dcc.Input(id="y2", type="number", placeholder="", debounce=True), ]), html.Div(id='data', children=df.to_json(), className='d-none'), html.Div([ dcc.Markdown(""" **Click Data** Click on points in the graph. """), html.Pre(id='click-data'), ]), html.Div([], id='spec-container'), ]) app.clientside_callback( output=Output('click-data', 'children'), inputs=[Input('rv-plot', 'clickData'), State('data', 'children')], clientside_function=ClientsideFunction(namespace='graphing', function_name='clickData')) app.clientside_callback( output=Output('rv-plot', 'figure'), inputs=[ Input('x1', 'value'), Input('x2', 'value'), Input('y1', 'value'), Input('y2', 'value'), State('data', 'children') ], clientside_function=ClientsideFunction(namespace='graphing', function_name='zoomfunc')) @app.callback(Output('spec-container', 'children'), Input('click-data', 'children')) def getGraph(children): if (children == None): raise PreventUpdate return [ children, dcc.Graph(id='spec-plot', figure=rv_figure, className="pt-5") ] with server.test_client() as client: client.get('/') app.index_string = render_template('data_page.html')