Closed droumis closed 4 months ago
hmmm... a simpler example works. so something about the original example code above is throwing things off...
import holoviews as hv; hv.extension('bokeh')
from holoviews.plotting.links import RangeToolLink
from holonote.annotate import Annotator
import pandas as pd
annotator = Annotator({"Time": float}, fields=["category"])
annotations_df = pd.DataFrame({'start': [.5], 'end': [.7], 'category': ['demo']})
annotator.define_annotations(annotations_df, Time=("start", "end"))
annotations_overlay = annotator.get_element("Time")
target = hv.Curve([1, 2], kdims='Time')
target_overlay = (annotations_overlay * target)
source = hv.Curve([1, 2], kdims='Time')
RangeToolLink(source, target, axes=["x", "y"])
(target_overlay + source * annotations_overlay).opts(shared_axes=False).cols(1)
https://github.com/holoviz/holonote/assets/6613202/06b6b762-1b8c-42a7-9763-2bc0a0fc8c28
Here is an MRE and some other examples that do not work. None of them is using HoloNote so I will move this issue to HoloViews.
It seems like it is caused by RangeToolLink
, subcoordinate_y
, and DynamicMap
.
import holoviews as hv
from holoviews.plotting.links import RangeToolLink
import pandas as pd
hv.extension("bokeh")
# Set up
curve_fn = lambda i: hv.Curve([i, i], kdims="Time", label=str(i)).opts(
subcoordinate_y=True
)
target = hv.Overlay([curve_fn(1), curve_fn(2)])
source = hv.Curve([1, 2], kdims="Time")
RangeToolLink(source, target, axes=["x", "y"])
# What HoloNote does behind the scene
annotations_overlay = hv.DynamicMap(lambda: hv.VSpans(([0], [1])))
target_overlay = annotations_overlay * target
# Other example 1 (does not work)
annotations_overlay = hv.DynamicMap(lambda: hv.VSpans(([0], [1])))
target_overlay = target * annotations_overlay
# Curve example 1 (does not work)
annotations_overlay = hv.DynamicMap(lambda: hv.Curve([0, 1]))
target_overlay = annotations_overlay * target
# Curve example 2 (does not work)
annotations_overlay = hv.DynamicMap(lambda: hv.Curve([0, 1]))
target_overlay = target * annotations_overlay
# Curve example 3 (does not work and seems to remove the link)
annotations_overlay = hv.Curve([0, 1])
target_overlay = target * annotations_overlay
# Final
(target_overlay + source).opts(shared_axes=False).cols(1)
Just checking which of my issues are still valid.
This is still a valid issue.
@Hoxbro , will you have a chance to look into this soon? It takes priority over scale bars, and it's right below the priority of finishing off the pandas index support to realize those performance gains 💪 . Hoping we can get all three completed by the end of March.
I'm a little confused by a few of these MROs posted above. Going through them 1-by-1:
# What HoloNote does behind the scene
annotations_overlay = hv.DynamicMap(lambda: hv.VSpans(([0], [1])))
target_overlay = annotations_overlay * target
Behavior I'm seeing is that the range tool correctly links the x-axis. It's also linking the y-axis of the first subcoordinate Curve. I'm not sure that's necessarily expected but it's poorly defined behavior. Would we expect all sub-coordinate ranges to be linked here?
# Other example 1 (does not work)
annotations_overlay = hv.DynamicMap(lambda: hv.VSpans(([0], [1])))
target_overlay = target * annotations_overlay
This does work, but it's not specified correctly. Since the target and source now share the exact same x-dimension the ranges end up linked, you could therefore use target_overlay + source.opts(axiswise=True)
.
The other cases are just variations of the first two. So the only real "issue" I see here is the behavior of the subcoordinate-y ranges. Do we expect all of them to be linked in these cases? I'd agree with that but it doesn't seem urgent since it's not needed for the CZI related tasks.
ALL software version info
holonote 0.1.0 holoviews 1.18.1
Description of expected behavior and the observed behavior
Overlaying annotations on a target plot prevents the RangeToolLink in a source plot from updating the dimension in the unbounded dimension of the annotation. For instance, with x-range annotations, the y-dimension link is broken.
Complete, minimal, self-contained example code that reproduces the issue
Code
``` import numpy as np import holoviews as hv from bokeh.models import HoverTool from holoviews.plotting.links import RangeToolLink from scipy.stats import zscore from holoviews.operation.datashader import rasterize hv.extension('bokeh') N_CHANNELS = 10 N_SECONDS = 5 SAMPLING_RATE = 200 INIT_FREQ = 2 # Initial frequency in Hz FREQ_INC = 5 # Frequency increment AMPLITUDE = 1 # Generate time and channel labels total_samples = N_SECONDS * SAMPLING_RATE time = np.linspace(0, N_SECONDS, total_samples) channels = [f'EEG {i}' for i in range(N_CHANNELS)] # Generate sine wave data data = np.array([AMPLITUDE * np.sin(2 * np.pi * (INIT_FREQ + i * FREQ_INC) * time) for i in range(N_CHANNELS)]) hover = HoverTool(tooltips=[ ("Channel", "@channel"), ("Time", "$x s"), ("Amplitude", "$y µV") ]) channel_curves = [] for channel, channel_data in zip(channels, data): ds = hv.Dataset((time, channel_data, channel), ["Time", "Amplitude", "channel"]) curve = hv.Curve(ds, "Time", ["Amplitude", "channel"], label=channel) curve.opts( subcoordinate_y=True, color="black", line_width=1, tools=[hover], ) channel_curves.append(curve) eeg_curves = hv.Overlay(channel_curves, kdims="Channel") annotator = Annotator({"Time": float}, fields=["category"]) annotations_df = pd.DataFrame({'start': [1], 'end': [2], 'category': ['demo']}) annotator.define_annotations(annotations_df, Time=("start", "end")) annotations_overlay = annotator.get_element("Time") eeg_app = (annotations_overlay * eeg_curves).opts( xlabel="Time (s)", ylabel="Channel", show_legend=False, aspect=3, responsive=True, ) y_positions = range(N_CHANNELS) yticks = [(i , ich) for i, ich in enumerate(channels)] z_data = zscore(data, axis=1) minimap = rasterize(hv.Image((time, y_positions , z_data), ["Time (s)", "Channel"], "Amplitude (uV)")) minimap = minimap.opts( cmap="RdBu_r", xlabel='Time (s)', alpha=.5, yticks=[yticks[0], yticks[-1]], height=150, responsive=True, default_tools=[], clim=(-z_data.std(), z_data.std()) ) RangeToolLink( minimap, eeg_curves, axes=["x", "y"], boundsx=(None, 2), boundsy=(None, 6.5) ) dashboard = (eeg_app + minimap * annotations_overlay).opts(merge_tools=False).cols(1) dashboard ```Screenshots or screencasts of the bug in action
https://github.com/holoviz/holonote/assets/6613202/4ad2e7e7-f9ed-4c51-b16b-200a1e0ee84b