예제 #1
0
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() == []
예제 #2
0
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")
예제 #3
0
def test_clsd001_simple_clientside_serverside_callback(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(
        ClientsideFunction(namespace="clientside", function_name="display"),
        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"')
예제 #4
0
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")
예제 #5
0
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}]]'),
    )
예제 #6
0
def test_clsd006_PreventUpdate(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_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")],
    )

    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", "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")
예제 #7
0
def test_clsd003_clientside_exceptions_halt_subsequent_updates(dash_duo):
    app = Dash(__name__, assets_folder="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")],
    )

    dash_duo.start_server(app)

    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)
예제 #8
0
def test_clsd004_clientside_multiple_outputs(dash_duo):
    app = Dash(__name__, assets_folder="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")],
    )

    dash_duo.start_server(app)

    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)
예제 #9
0
def test_clsd009_clientside_callback_context_triggered(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="triggered_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", "")

    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',
    )
예제 #10
0
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")
예제 #11
0
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")
예제 #12
0
    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,
                    }
                )
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 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
예제 #15
0
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"}]]'),
    )
예제 #16
0
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