def test_selection1d_dynamic_map(self): # Create dynamic map that inputs selection1d, returns overlay of scatter on # selected points scatter = Scatter([[0, 0], [1, 1], [2, 2]]) selection1d = Selection1D(source=scatter) dmap = DynamicMap( lambda index: scatter.iloc[index].opts(size=len(index) + 1), streams=[selection1d]) # Convert to Dash components = to_dash(self.app, [scatter, dmap], reset_button=True) # Check returned components self.assertIsInstance(components, DashComponents) self.assertEqual(len(components.graphs), 2) self.assertEqual(len(components.kdims), 0) self.assertIsInstance(components.store, Store) self.assertEqual(len(components.resets), 1) # Get arguments passed to @app.callback decorator decorator_args = list(self.app.callback.call_args_list[0])[0] outputs, inputs, states = decorator_args # Check outputs expected_outputs = [(g.id, "figure") for g in components.graphs] + \ [(components.store.id, "data")] self.assertEqual([(output.component_id, output.component_property) for output in outputs], expected_outputs) # Check inputs expected_inputs = [(g.id, prop) for g in components.graphs for prop in ["selectedData", "relayoutData"] ] + [(components.resets[0].id, "n_clicks")] self.assertEqual( [(ip.component_id, ip.component_property) for ip in inputs], expected_inputs, ) # Check State expected_state = [(components.store.id, "data")] self.assertEqual( [(state.component_id, state.component_property) for state in states], expected_state, ) # Check initial figures fig1 = components.graphs[0].figure fig2 = components.graphs[1].figure # Figure holds the scatter trace self.assertEqual(len(fig2["data"]), 1) # Check expected marker size self.assertEqual(fig2["data"][0]["marker"]["size"], 1) self.assertEqual(list(fig2["data"][0]["x"]), []) self.assertEqual(list(fig2["data"][0]["y"]), []) # Get callback function callback_fn = self.app.callback.return_value.call_args[0][0] # mimic initial callback invocation store_value = encode_store_data( {"streams": { id(selection1d): selection1d.contents }}) # Update store, then mimick a selection on scatter figure # store_value = new_store with patch.object( CallbackContext, "triggered", [{ "prop_id": inputs[0].component_id + ".selectedData" }]): [fig1, fig2, new_store] = callback_fn( { "points": [{ "curveNumber": 0, "pointNumber": 0, "pointIndex": 0, }, { "curveNumber": 0, "pointNumber": 2, "pointIndex": 2, }] }, {}, 0, store_value) # Figure holds the scatter trace self.assertEqual(len(fig2["data"]), 1) # Check expected marker size self.assertEqual(fig2["data"][0]["marker"]["size"], 3) self.assertEqual(list(fig2["data"][0]["x"]), [0, 2]) self.assertEqual(list(fig2["data"][0]["y"]), [0, 2]) # Check that store was updated self.assertEqual(decode_store_data(new_store), { "streams": { id(selection1d): { "index": [0, 2] } }, "kdims": {} }) # Click reset button store = new_store with patch.object(CallbackContext, "triggered", [{ "prop_id": components.resets[0].id + ".n_clicks" }]): [fig1, fig2, new_store] = callback_fn({}, {}, 1, store) # Figure holds the scatter trace self.assertEqual(len(fig2["data"]), 1) # Check expected marker size self.assertEqual(fig2["data"][0]["marker"]["size"], 1) self.assertEqual(list(fig2["data"][0]["x"]), []) self.assertEqual(list(fig2["data"][0]["y"]), []) # Check that store was updated self.assertEqual( decode_store_data(new_store), { "streams": { id(selection1d): { "index": [] } }, 'reset_nclicks': 1, "kdims": {} }, )
def test_boundsxy_dynamic_map(self): # Build Holoviews Elements scatter = Scatter([0, 0]) boundsxy = BoundsXY(source=scatter) dmap = DynamicMap(lambda bounds: Bounds(bounds) if bounds is not None else Bounds((0, 0, 0, 0)), streams=[boundsxy]) # Convert to Dash components = to_dash(self.app, [scatter, dmap], reset_button=True) # Check returned components self.assertIsInstance(components, DashComponents) self.assertEqual(len(components.graphs), 2) self.assertEqual(len(components.kdims), 0) self.assertIsInstance(components.store, Store) self.assertEqual(len(components.resets), 1) # Get arguments passed to @app.callback decorator decorator_args = list(self.app.callback.call_args_list[0])[0] outputs, inputs, states = decorator_args # Check outputs expected_outputs = [(g.id, "figure") for g in components.graphs] + \ [(components.store.id, "data")] self.assertEqual([(output.component_id, output.component_property) for output in outputs], expected_outputs) # Check inputs expected_inputs = [(g.id, prop) for g in components.graphs for prop in ["selectedData", "relayoutData"] ] + [(components.resets[0].id, "n_clicks")] self.assertEqual( [(ip.component_id, ip.component_property) for ip in inputs], expected_inputs, ) # Check State expected_state = [(components.store.id, "data")] self.assertEqual( [(state.component_id, state.component_property) for state in states], expected_state, ) # Check initial figures fig1 = components.graphs[0].figure fig2 = components.graphs[1].figure self.assertEqual(fig1["data"][0]["type"], "scatter") # Second figure holds the bounds element self.assertEqual(len(fig2["data"]), 0) self.assertEqual(len(fig2["layout"]["shapes"]), 1) self.assertEqual(fig2["layout"]["shapes"][0]["path"], "M0 0L0 0L0 0L0 0L0 0Z") # Get callback function callback_fn = self.app.callback.return_value.call_args[0][0] # # mimic initial callback invocation store_value = encode_store_data( {"streams": { id(boundsxy): boundsxy.contents }}) # Update store, then mimic a box selection on scatter figure # store_value = new_store with patch.object( CallbackContext, "triggered", [{ "prop_id": inputs[0].component_id + ".selectedData" }]): [fig1, fig2, new_store] = callback_fn({"range": { "x": [1, 2], "y": [3, 4] }}, {}, {}, {}, 0, store_value) # First figure is the scatter trace self.assertEqual(fig1["data"][0]["type"], "scatter") # Second figure holds the bounds element self.assertEqual(len(fig2["data"]), 0) self.assertEqual(len(fig2["layout"]["shapes"]), 1) self.assertEqual( fig2["layout"]["shapes"][0]["path"], "M1 3L1 4L2 4L2 3L1 3Z", ) # Check that store was updated self.assertEqual(decode_store_data(new_store), { "streams": { id(boundsxy): { "bounds": (1, 3, 2, 4) } }, "kdims": {} }) # Click reset button with patch.object(CallbackContext, "triggered", [{ "prop_id": components.resets[0].id + ".n_clicks" }]): [fig1, fig2, new_store] = callback_fn({"range": { "x": [1, 2], "y": [3, 4] }}, {}, {}, {}, 1, store_value) # First figure is the scatter trace self.assertEqual(fig1["data"][0]["type"], "scatter") # Second figure holds reset bounds elemnt self.assertEqual(len(fig2["data"]), 0) self.assertEqual(len(fig2["layout"]["shapes"]), 1) self.assertEqual(fig2["layout"]["shapes"][0]["path"], "M0 0L0 0L0 0L0 0L0 0Z") # Reset button should clear bounds in store self.assertEqual( decode_store_data(new_store), { "streams": { id(boundsxy): { "bounds": None } }, "reset_nclicks": 1, "kdims": {} })
def test_rangexy_dynamic_map(self): # Create dynamic map that inputs rangexy, returns scatter on bounds scatter = Scatter([[0, 1], [0, 1]], kdims=["x"], vdims=["y"]) rangexy = RangeXY(source=scatter) def dmap_fn(x_range, y_range): x_range = (0, 1) if x_range is None else x_range y_range = (0, 1) if y_range is None else y_range return Scatter( [[x_range[0], y_range[0]], [x_range[1], y_range[1]]], kdims=["x1"], vdims=["y1"]) dmap = DynamicMap(dmap_fn, streams=[rangexy]) # Convert to Dash components = to_dash(self.app, [scatter, dmap], reset_button=True) # Check returned components self.assertIsInstance(components, DashComponents) self.assertEqual(len(components.graphs), 2) self.assertEqual(len(components.kdims), 0) self.assertIsInstance(components.store, Store) self.assertEqual(len(components.resets), 1) # Get arguments passed to @app.callback decorator decorator_args = list(self.app.callback.call_args_list[0])[0] outputs, inputs, states = decorator_args # Check outputs expected_outputs = [(g.id, "figure") for g in components.graphs] + \ [(components.store.id, "data")] self.assertEqual([(output.component_id, output.component_property) for output in outputs], expected_outputs) # Check inputs expected_inputs = [(g.id, prop) for g in components.graphs for prop in ["selectedData", "relayoutData"] ] + [(components.resets[0].id, "n_clicks")] self.assertEqual( [(ip.component_id, ip.component_property) for ip in inputs], expected_inputs, ) # Check State expected_state = [(components.store.id, "data")] self.assertEqual( [(state.component_id, state.component_property) for state in states], expected_state, ) # Get callback function callback_fn = self.app.callback.return_value.call_args[0][0] # mimic initial callback invocation store_value = encode_store_data( {"streams": { id(rangexy): rangexy.contents }}) with patch.object( CallbackContext, "triggered", [{ "prop_id": components.graphs[0].id + ".relayoutData" }]): [fig1, fig2, new_store] = callback_fn({}, { "xaxis.range[0]": 1, "xaxis.range[1]": 3, "yaxis.range[0]": 2, "yaxis.range[1]": 4 }, {}, {}, None, store_value) # First figure is the scatter trace self.assertEqual(fig1["data"][0]["type"], "scatter") # Second figure holds the bounds element self.assertEqual(len(fig2["data"]), 1) self.assertEqual(list(fig2["data"][0]["x"]), [1, 3]) self.assertEqual(list(fig2["data"][0]["y"]), [2, 4]) # Check updated store self.assertEqual( decode_store_data(new_store), { "streams": { id(rangexy): { 'x_range': (1, 3), 'y_range': (2, 4) } }, "kdims": {} })