Beispiel #1
0
    def setUp(self):
        if 'plotly' not in Store.renderers and pn is not None:
            raise SkipTest("Plotly and Panel required to test rendering.")

        self.renderer = PlotlyRenderer.instance()
        self.nbcontext = Renderer.notebook_context
        self.comm_manager = Renderer.comm_manager
        with param.logging_level('ERROR'):
            Renderer.notebook_context = False
            Renderer.comm_manager = CommManager
Beispiel #2
0
    def setUp(self):
        if 'plotly' not in Store.renderers or None in (pn, PlotlyRenderer):
            raise SkipTest("Plotly and Panel required to test rendering.")

        self.previous_backend = Store.current_backend
        Store.current_backend = 'plotly'
        self.renderer = PlotlyRenderer.instance()
        self.nbcontext = Renderer.notebook_context
        self.comm_manager = Renderer.comm_manager
        with param.logging_level('ERROR'):
            Renderer.notebook_context = False
            Renderer.comm_manager = CommManager
Beispiel #3
0
    def update_figure(*args):
        triggered_prop_ids = {
            entry["prop_id"]
            for entry in callback_context.triggered
        }

        # Unpack args
        selected_dicts = [args[j] or {} for j in range(0, num_figs * 2, 2)]
        relayout_dicts = [args[j] or {} for j in range(1, num_figs * 2, 2)]

        # Get kdim values
        kdim_values = {}
        for i, kdim in zip(range(num_figs * 2, num_figs * 2 + len(all_kdims)),
                           all_kdims):
            kdim_values[kdim] = args[i]

        # Get store
        store_data = decode_store_data(args[-1])
        reset_nclicks = 0
        if reset_button:
            reset_nclicks = args[-2] or 0
            prior_reset_nclicks = store_data.get("reset_nclicks", 0)
            if reset_nclicks != prior_reset_nclicks:
                store_data["reset_nclicks"] = reset_nclicks

                # clear stream values
                store_data["streams"] = copy.deepcopy(initial_stream_contents)
                selected_dicts = [None for _ in selected_dicts]
                relayout_dicts = [None for _ in relayout_dicts]

        # Init store data
        if store_data is None:
            store_data = {"streams": {}}

        # Update store_data with interactive stream values
        for fig_ind, fig_dict in enumerate(initial_fig_dicts):
            graph_id = graph_ids[fig_ind]
            # plotly_stream_types
            for plotly_stream_type, uid_to_streams_for_type in uid_to_stream_ids.items(
            ):
                panel_prop = plotly_stream_type.callback_property
                if panel_prop == "selected_data":
                    if graph_id + ".selectedData" in triggered_prop_ids:
                        # Only update selectedData values that just changed.
                        # This way we don't the the may have been cleared in the
                        # store above
                        stream_event_data = plotly_stream_type.get_event_data_from_property_update(
                            selected_dicts[fig_ind],
                            initial_fig_dicts[fig_ind])
                        for uid, event_data in stream_event_data.items():
                            if uid in uid_to_streams_for_type:
                                for stream_id in uid_to_streams_for_type[uid]:
                                    store_data["streams"][
                                        stream_id] = event_data
                elif panel_prop == "viewport":
                    if graph_id + ".relayoutData" in triggered_prop_ids:
                        stream_event_data = plotly_stream_type.get_event_data_from_property_update(
                            relayout_dicts[fig_ind],
                            initial_fig_dicts[fig_ind])

                        for uid, event_data in stream_event_data.items():
                            if event_data["x_range"] is not None or event_data[
                                    "y_range"] is not None:
                                if uid in uid_to_streams_for_type:
                                    for stream_id in uid_to_streams_for_type[
                                            uid]:
                                        store_data["streams"][
                                            stream_id] = event_data

        # Update store with derived/history stream values
        for output_id in reversed(stream_callbacks):
            stream_callback = stream_callbacks[output_id]
            input_ids = stream_callback.input_ids
            fn = stream_callback.fn
            output_id = stream_callback.output_id

            input_values = [
                store_data["streams"][input_id] for input_id in input_ids
            ]
            output_value = fn(*input_values)
            store_data["streams"][output_id] = output_value

        figs = [None] * num_figs
        for fig_ind, (fn, stream_ids) in fig_to_fn_stream_ids.items():
            fig_kdim_values = [
                kdim_values[kd] for kd in kdims_per_fig[fig_ind]
            ]
            stream_values = [
                store_data["streams"][stream_id] for stream_id in stream_ids
            ]
            hvobj = fn(*(fig_kdim_values + stream_values))
            plot = PlotlyRenderer.get_plot(hvobj)
            fig = plot_to_figure(plot, reset_nclicks=reset_nclicks).to_dict()
            figs[fig_ind] = fig

        return figs + [encode_store_data(store_data)]
Beispiel #4
0
def to_dash(app, hvobjs, reset_button=False, graph_class=dcc.Graph):
    """
    Build Dash components and callbacks from a collection of HoloViews objects

    Args:
        app: dash.Dash application instance
        hvobjs: List of HoloViews objects to build Dash components from
        reset_button: If True, construct a Button component that, which clicked, will
            reset the interactive stream values associated with the provided HoloViews
            objects to their initial values. Defaults to False.
        graph_class: Class to use when creating Graph components, one of dcc.Graph
            (default) or ddk.Graph.

    Returns:
        DashComponents named tuple with properties:
            - graphs: List of graph components (with type matching the input
                graph_class argument) with order corresponding to the order
                of the input hvobjs list.
            - resets: List of reset buttons that can be used to reset figure state.
                List has length 1 if reset_button=True and is empty if
                reset_button=False.
            - kdims: Dict from kdim names to Dash Components that can be used to
                set the corresponding kdim value.
            - store: dcc.Store the must be included in the app layout
            - children: Single list of all components above. The order is graphs,
                kdims, resets, and then the store.
    """
    # Number of figures
    num_figs = len(hvobjs)

    # Initialize component properties
    reset_components = []
    graph_components = []
    kdim_components = {}

    # Initialize inputs / outputs / states list
    outputs = []
    inputs = []
    states = []

    # Initialize other
    plots = []
    graph_ids = []
    initial_fig_dicts = []
    all_kdims = OrderedDict()
    kdims_per_fig = []

    # Initialize stream mappings
    uid_to_stream_ids = {}
    fig_to_fn_stream = {}
    fig_to_fn_stream_ids = {}

    # Plotly stream types
    plotly_stream_types = [
        RangeXYCallback, RangeXCallback, RangeYCallback, Selection1DCallback,
        BoundsXYCallback, BoundsXCallback, BoundsYCallback
    ]

    for i, hvobj in enumerate(hvobjs):

        fn_spec = to_function_spec(hvobj)

        fig_to_fn_stream[i] = fn_spec
        kdims_per_fig.append(list(fn_spec.kdims))
        all_kdims.update(fn_spec.kdims)

        # Convert to figure once so that we can map streams to axes
        plot = PlotlyRenderer.get_plot(hvobj)
        plots.append(plot)

        fig = plot_to_figure(plot, reset_nclicks=0).to_dict()
        initial_fig_dicts.append(fig)

        # Build graphs
        graph_id = 'graph-' + str(uuid.uuid4())
        graph_ids.append(graph_id)
        graph = graph_class(id=graph_id,
                            figure=fig,
                            config={"scrollZoom": True})
        graph_components.append(graph)

        # Build dict from trace uid to plotly callback object
        plotly_streams = {}
        for plotly_stream_type in plotly_stream_types:
            for t in fig["data"]:
                if t.get("uid", None) in plotly_stream_type.instances:
                    plotly_streams.setdefault(plotly_stream_type, {})[t["uid"]] = \
                        plotly_stream_type.instances[t["uid"]]

        # Build dict from trace uid to list of connected HoloViews streams
        for plotly_stream_type, streams_for_type in plotly_streams.items():
            for uid, cb in streams_for_type.items():
                uid_to_stream_ids.setdefault(plotly_stream_type,
                                             {}).setdefault(uid, []).extend([
                                                 id(stream)
                                                 for stream in cb.streams
                                             ])

        outputs.append(
            Output(component_id=graph_id, component_property='figure'))
        inputs.extend([
            Input(component_id=graph_id, component_property='selectedData'),
            Input(component_id=graph_id, component_property='relayoutData')
        ])

    # Build Store and State list
    store_data = {"streams": {}}
    store_id = 'store-' + str(uuid.uuid4())
    states.append(State(store_id, 'data'))

    # Store holds mapping from id(stream) -> stream.contents for:
    #   - All extracted streams (including derived)
    #   - All input streams for History and Derived streams.
    for fn_spec in fig_to_fn_stream.values():
        populate_store_with_stream_contents(store_data, fn_spec.streams)

    # Initialize empty list of (input_ids, output_id, fn) triples. For each
    #    Derived/History stream, prepend list with triple. Process in
    #    breadth-first order so all inputs to a triple are guaranteed to be earlier
    #    in the list. History streams will input and output their own id, which is
    #    fine.
    stream_callbacks = OrderedDict()
    for fn_spec in fig_to_fn_stream.values():
        populate_stream_callback_graph(stream_callbacks, fn_spec.streams)

    # For each Figure function, save off list of ids for the streams whose contents
    #    should be passed to the function.
    for i, fn_spec in fig_to_fn_stream.items():
        fig_to_fn_stream_ids[i] = fn_spec.fn, [
            id(stream) for stream in fn_spec.streams
        ]

    # Add store output
    store = dcc.Store(
        id=store_id,
        data=encode_store_data(store_data),
    )
    outputs.append(Output(store_id, 'data'))

    # Save copy of initial stream contents
    initial_stream_contents = copy.deepcopy(store_data["streams"])

    # Add kdim sliders
    kdim_uuids = []
    for kdim_name, (kdim_label, kdim_range) in all_kdims.items():
        slider_uuid = str(uuid.uuid4())
        slider_id = kdim_name + "-" + slider_uuid
        slider_label_id = kdim_name + "-label-" + slider_uuid
        kdim_uuids.append(slider_uuid)

        html_label = html.Label(id=slider_label_id, children=kdim_label)
        if isinstance(kdim_range, list):
            # list of slider values
            slider = html.Div(children=[
                html_label,
                dcc.Slider(id=slider_id,
                           min=kdim_range[0],
                           max=kdim_range[-1],
                           step=None,
                           marks={m: ""
                                  for m in kdim_range},
                           value=kdim_range[0])
            ])
        else:
            # Range of slider values
            slider = html.Div(children=[
                html_label,
                dcc.Slider(id=slider_id,
                           min=kdim_range[0],
                           max=kdim_range[-1],
                           step=(kdim_range[-1] - kdim_range[0]) / 11.0,
                           value=kdim_range[0])
            ])
        kdim_components[kdim_name] = slider
        inputs.append(Input(component_id=slider_id,
                            component_property="value"))

    # Add reset button
    if reset_button:
        reset_id = 'reset-' + str(uuid.uuid4())
        reset_button = html.Button(id=reset_id, children="Reset")
        inputs.append(
            Input(component_id=reset_id, component_property='n_clicks'))
        reset_components.append(reset_button)

    # Register Graphs/Store callback
    @app.callback(outputs, inputs, states)
    def update_figure(*args):
        triggered_prop_ids = {
            entry["prop_id"]
            for entry in callback_context.triggered
        }

        # Unpack args
        selected_dicts = [args[j] or {} for j in range(0, num_figs * 2, 2)]
        relayout_dicts = [args[j] or {} for j in range(1, num_figs * 2, 2)]

        # Get kdim values
        kdim_values = {}
        for i, kdim in zip(range(num_figs * 2, num_figs * 2 + len(all_kdims)),
                           all_kdims):
            kdim_values[kdim] = args[i]

        # Get store
        store_data = decode_store_data(args[-1])
        reset_nclicks = 0
        if reset_button:
            reset_nclicks = args[-2] or 0
            prior_reset_nclicks = store_data.get("reset_nclicks", 0)
            if reset_nclicks != prior_reset_nclicks:
                store_data["reset_nclicks"] = reset_nclicks

                # clear stream values
                store_data["streams"] = copy.deepcopy(initial_stream_contents)
                selected_dicts = [None for _ in selected_dicts]
                relayout_dicts = [None for _ in relayout_dicts]

        # Init store data
        if store_data is None:
            store_data = {"streams": {}}

        # Update store_data with interactive stream values
        for fig_ind, fig_dict in enumerate(initial_fig_dicts):
            graph_id = graph_ids[fig_ind]
            # plotly_stream_types
            for plotly_stream_type, uid_to_streams_for_type in uid_to_stream_ids.items(
            ):
                panel_prop = plotly_stream_type.callback_property
                if panel_prop == "selected_data":
                    if graph_id + ".selectedData" in triggered_prop_ids:
                        # Only update selectedData values that just changed.
                        # This way we don't the the may have been cleared in the
                        # store above
                        stream_event_data = plotly_stream_type.get_event_data_from_property_update(
                            selected_dicts[fig_ind],
                            initial_fig_dicts[fig_ind])
                        for uid, event_data in stream_event_data.items():
                            if uid in uid_to_streams_for_type:
                                for stream_id in uid_to_streams_for_type[uid]:
                                    store_data["streams"][
                                        stream_id] = event_data
                elif panel_prop == "viewport":
                    if graph_id + ".relayoutData" in triggered_prop_ids:
                        stream_event_data = plotly_stream_type.get_event_data_from_property_update(
                            relayout_dicts[fig_ind],
                            initial_fig_dicts[fig_ind])

                        for uid, event_data in stream_event_data.items():
                            if event_data["x_range"] is not None or event_data[
                                    "y_range"] is not None:
                                if uid in uid_to_streams_for_type:
                                    for stream_id in uid_to_streams_for_type[
                                            uid]:
                                        store_data["streams"][
                                            stream_id] = event_data

        # Update store with derived/history stream values
        for output_id in reversed(stream_callbacks):
            stream_callback = stream_callbacks[output_id]
            input_ids = stream_callback.input_ids
            fn = stream_callback.fn
            output_id = stream_callback.output_id

            input_values = [
                store_data["streams"][input_id] for input_id in input_ids
            ]
            output_value = fn(*input_values)
            store_data["streams"][output_id] = output_value

        figs = [None] * num_figs
        for fig_ind, (fn, stream_ids) in fig_to_fn_stream_ids.items():
            fig_kdim_values = [
                kdim_values[kd] for kd in kdims_per_fig[fig_ind]
            ]
            stream_values = [
                store_data["streams"][stream_id] for stream_id in stream_ids
            ]
            hvobj = fn(*(fig_kdim_values + stream_values))
            plot = PlotlyRenderer.get_plot(hvobj)
            fig = plot_to_figure(plot, reset_nclicks=reset_nclicks).to_dict()
            figs[fig_ind] = fig

        return figs + [encode_store_data(store_data)]

    # Register key dimension slider callbacks
    # Install callbacks to update kdim labels based on slider values
    for i, kdim_name in enumerate(all_kdims):
        kdim_label = all_kdims[kdim_name][0]
        kdim_slider_id = kdim_name + "-" + kdim_uuids[i]
        kdim_label_id = kdim_name + "-label-" + kdim_uuids[i]

        @app.callback(
            Output(component_id=kdim_label_id, component_property="children"),
            [Input(component_id=kdim_slider_id, component_property="value")])
        def update_kdim_label(value, kdim_label=kdim_label):
            return "{kdim_label}: {value:.2f}".format(kdim_label=kdim_label,
                                                      value=value)

    # Collect Dash components into DashComponents namedtuple
    components = DashComponents(
        graphs=graph_components,
        kdims=kdim_components,
        resets=reset_components,
        store=store,
        children=(graph_components + list(kdim_components.values()) +
                  reset_components + [store]))

    return components