Hi,
Intro:
So I am doing a parser where I plot and interact with 5-10 millions data.
In which I want to add cursors created with a dynamicmap that can be manipulated with boundsx and also the input float from panel. The dynamicmap is streamed from the input float. When I change the boundsx I create an event on the input stream which trigger the dynamicmap where the vline for the cursor move.
Problem:
Now the problem, is when I zoom on my plot and change the cursors position, either with boundsx or stream, the “reset” tool is not working as expected.
For example:
Position Z0, updated the data, the whole plot is visible. If I zoom or dezoom the press on “reset” tool, the plot come back into position Z0.
I zoom, position Z1 then and add cursors. Now when I press on “reset” tool. The will come back on position Z1. Either I zoom or dezoom and press “reset”, plot will always go back to position Z1 instead of Z0.
I investigated this problem, and I found that if I remove the downsample parameter from the hvplot the problem disappear. But as i need to interact with millions of data and need to have a fluid interaction I need this parameter set. Also, if I use remove the input and stream the dynamicmap on the boundx there is no problem. But as I need to put cursor on precise point, the input is mandatory.
Part of my code:
# Create the main line plot
self._main_plot = df.hvplot.line(
x='x',
y=y_list,
xlabel="Time(s)",
ylabel="Current(mA)",
line_color=color_list,
datashade=False,
downsample=True,
precompute=True,
colorbar=False,
tools=["box_zoom", 'save', 'pan', 'reset', 'xbox_select'],
responsive=True,
min_height=200,
max_height=1000,
frame_height=500,
#shared_axes=False,
title=self.graph_title,
legend="bottom",
autorange=self.autorange_state,
)
# Create the overview plot
overview = df.hvplot.line(
x='x',
y=y_list,
xlabel="Time(s)",
ylabel="Current(mA)",
yticks=[0, 5],
tools=[],
min_height=100,
max_height=200,
datashade=False,
downsample=True,
precompute=True,
responsive=True,
shared_axes=False,
line_color=color_list,
)
# Link the overview and main plots using RangeToolLink
RangeToolLink(overview, self._main_plot)
# Create BoundsX stream for interaction and data updates
self.bounds_stream = hv.streams.BoundsX(source=self._main_plot,boundsx=(0, 0))
self.bounds_stream.param.watch(self.handle_bounds_update, ['boundsx'])
self.vline_cursor_l = hv.DynamicMap(self.vline_l, streams=[self.input_stream_0])
self.vline_cursor_r = hv.DynamicMap(self.vline_r, streams=[self.input_stream_1])
# Monitor x_range with RangeX stream for widget updates
rxy = hv.streams.RangeX(source=self._main_plot)
rxy.param.watch(self.update_widget, 'x_range')
main_plot = self._main_plot * self.vline_cursor_l * self.vline_cursor_r
# Add backend_opts to restrict overview plot range
overview.opts(backend_opts={"x_range.bounds": (0, df['x'].iloc[-1]), "y_range.bounds": (0, 5)},
default_tools=[], active_tools=[], show_legend=False)
# Combine main plot with dynamic VLine and overview plot
combined_layout = (main_plot + overview).cols(1)
#combined_layout.opts(shared_axes=False)
return combined_layout
@staticmethod
def vline_l(value):
return hv.VLine(value).opts(line_color="green", line_width=2)
@staticmethod
def vline_r(value):
return hv.VLine(value).opts(line_color="green", line_width=2)
def handle_bounds_update(self, event):
boundsx = event.new # `[start, end]` bounds of x-axis selection
self.cursor_l, self.cursor_r = boundsx # Extract start and end x-coordinates
self.input_cursor_0.value, self.input_cursor_1.value = boundsx
def update_input_stream_0(self, new_value):
self.input_stream_0.event(value=new_value)
self.cursor_l = new_value
self.cursor_range_info()
def update_input_stream_1(self, new_value):
self.input_stream_1.event(value=new_value)
self.cursor_r = new_value
self.cursor_range_info()
def layout(self):
self.input_cursor_0 = pn.widgets.FloatInput(name='Cursor L', value=0.0, step=1, start=0, width=100, format='0.00')
self.input_stream_0 = hv.streams.Stream.define('CursorStream', value=0.0)()
pn.bind(self.update_input_stream_0, self.input_cursor_0.param.value, watch=True)
self.input_cursor_1 = pn.widgets.FloatInput(name='Cursor R', value=0.0, step=1, start=0, width=100, format='0.00')
self.input_stream_1 = hv.streams.Stream.define('CursorStream', value=0.0)()
pn.bind(self.update_input_stream_1, self.input_cursor_1.param.value, watch=True)