holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.66k stars 396 forks source link

Support hit-tested group-wise wheel zoom renderers for subcoordinate_y #6268

Open droumis opened 4 weeks ago

droumis commented 4 weeks ago

Should be tested and work with the HoloViews doc's eeg example

See Bokeh PR for incoming 3.5 functionality and description of behavior: https://github.com/bokeh/bokeh/pull/13826

Pure Bokeh example ```python from bokeh.layouts import column from bokeh.plotting import figure, curdoc import numpy as np from bokeh.core.properties import field from bokeh.layouts import column, row from bokeh.models import (ColumnDataSource, CustomJS, Div, FactorRange, HoverTool, Range1d, Switch, WheelZoomTool, ZoomInTool, ZoomOutTool, GroupByModels) from bokeh.palettes import Category10 from bokeh.plotting import figure n_eeg_channels = 7 n_pos_channels = 3 n_channels = n_eeg_channels + n_pos_channels n_seconds = 15 total_samples = 512*n_seconds time = np.linspace(0, n_seconds, total_samples) data = np.random.randn(n_channels, total_samples).cumsum(axis=1) channels = [f"EEG {i}" for i in range(n_eeg_channels)] + [f"POS {i}" for i in range(n_pos_channels)] hover = HoverTool(tooltips=[ ("Channel", "$name"), ("Time", "$x s"), ("Amplitude", "$y μV"), ]) x_range = Range1d(start=time.min(), end=time.max()) y_range = FactorRange(factors=channels) p = figure(x_range=x_range, y_range=y_range, lod_threshold=None, tools="pan,reset,xcrosshair") source = ColumnDataSource(data=dict(time=time)) eeg_renderers = [] pos_renderers = [] for i, channel in enumerate(channels): is_eeg = channel.startswith('EEG') xy = p.subplot( x_source=p.x_range, y_source=Range1d(start=data[i].min(), end=data[i].max()), x_target=p.x_range, y_target=Range1d(start=i, end=i + 1), ) source.data[channel] = data[i] if is_eeg: line = xy.line(field("time"), field(channel), color='black', source=source, name=channel) eeg_renderers.append(line) else: line = xy.line(field("time"), field(channel), color=Category10[10][i], source=source, name=channel) pos_renderers.append(line) all_renderers = eeg_renderers + pos_renderers level = 1 hit_test = True behavior = GroupByModels(groups=[eeg_renderers, pos_renderers]) ywheel_zoom = WheelZoomTool(renderers=all_renderers, level=level, hit_test=hit_test, hit_test_mode="hline", hit_test_behavior=behavior, dimensions="height") xwheel_zoom = WheelZoomTool(renderers=all_renderers, level=level, dimensions="width") zoom_in = ZoomInTool(renderers=all_renderers, level=level, dimensions="height") zoom_out = ZoomOutTool(renderers=all_renderers, level=level, dimensions="height") p.add_tools(ywheel_zoom, xwheel_zoom, zoom_in, zoom_out, hover) p.toolbar.active_scroll = ywheel_zoom level_switch = Switch(active=level == 1) level_switch.js_on_change("active", CustomJS( args=dict(tools=[ywheel_zoom, zoom_in, zoom_out]), code=""" export default ({tools}, obj) => { const level = obj.active ? 1 : 0 for (const tool of tools) { tool.level = level } } """)) hit_test_switch = Switch(active=hit_test) hit_test_switch.js_on_change("active", CustomJS( args=dict(tool=ywheel_zoom), code=""" export default ({tool}, obj) => { tool.hit_test = obj.active } """)) layout = column( row(Div(text="Zoom sub-coordinates:"), level_switch), row(Div(text="Zoom hit-tested:"), hit_test_switch), p, ) curdoc().add_root(layout) ```

https://github.com/bokeh/bokeh/assets/6613202/9bc53421-937c-47aa-9bcc-23dc4b7ec94c