Repository: holoviz/holoviews

Issue:
Overlay Zoom Hangup
Having an issue where some sets of data cause Holoviews plots to freeze up when zooming

My goal is to create a plotting class that takes as input a nested dictionary containing time series time/data.  The "top level" keys are parameter labels, and the second-level keys are test names.  A layout is created with one figure per parameter, and each figure contains a time series Curve for each test.  I expect this input data to be very large, so the plots get the Datashader treatment.

When I use "real" data (pulled from a data mining script), the entire layout is created as expected, but when I zoom (scroll or box) my CPU immediately gets busy and the figure remains unresponsive, and I then I kill the process.  However, if build an input dataset that is generic (just some sine waves), everything plots/zooms as expected.

I think the fundamental question is, why would my "real" dataset cause problems for the zoom function?

Here's my "real" dataset that shows the issue:
[data.txt](https://github.com/holoviz/holoviews/files/10197973/data.txt)

Here's my MRE.  Commenting out the `data_source` declarations at the end lets you choose between loading the attached "real" dataset and the self-generated sine wave data:
```
import holoviews as hv
import panel as pn
from holoviews.operation.datashader import datashade
import datashader as ds
import pandas as pd
import json
from math import sin

hv.extension('bokeh')


class MultiPlot:

    def __init__(self, data, max_plots=6, plot_width=800, plot_height=150, cnorm="eq_hist", line_width=2,
                 pixel_ratio=2):

        print('Normalizing time array for all events (to epoch)...')
        for top_key in data.keys():
            for sec_key in data[top_key].keys():
                # print(top_key, sec_key)
                try:
                    new_index = (data[top_key][sec_key].index - data[top_key][sec_key].index[0]).total_seconds()
                    data[top_key][sec_key].index = new_index
                except IndexError:
                    print('WARNING: IndexError while normalizing time array to epoch, for keys:', top_key, sec_key)
                    data[top_key][sec_key].index = pd.to_timedelta(data[top_key][sec_key].index).total_seconds()

        print('Defining Curves...')
        curves_dict = {top_key: {sec_key: hv.Curve((sec_value.index.values,
                                                    sec_value.values), kdims=['Time'], vdims=[top_key])
                                 for sec_key, sec_value in top_value.items()
                                 }
                       for top_key, top_value in data.items()}

        # Create list of overlay objects; one per top-level key, with each containing one Curve per secondary-key
        print('Defining overlay...')
        self.overlay = [datashade(hv.NdOverlay(curves), line_width=line_width, pixel_ratio=pixel_ratio,
                                   cnorm=cnorm, aggregator=ds.count()
                                   ).opts(width=plot_width, tools=['hover']).relabel(top_key)
                        for top_key, curves in list(curves_dict.items())]
        # Create Layout to plot each Overlay figure in a single column
        print('Defining layout...')
        self.layout = hv.Layout(self.overlay).cols(1)

    def show(self):
        pn.serve(self.layout)

def save_json(data):
    """Takes nested dictionary of Series objects, saves Series as JSON"""
    out_dict = dict()
    for k1, v1 in data.items():
        out_dict.update({'parameter' + k1[-1]: {}})
        for k2, v2 in v1.items():
            out_dict['parameter' + k1[-1]].update({'test' + k2[-10:-6]: v2.to_json()})

def read_json(filename):
    with open(filename, 'r') as f:
        data_dict = json.load(f)
        return {k1: {k2: pd.read_json(v2, typ='series')
                     for k2, v2 in v1.items()}
                for k1, v1 in data_dict.items()
                }

def create_generic_data(level1_parm_count, level2_parm_count, parm_sample_count):
    """Generates generic data dictionary"""
    l1_parms = [f'L1P_{str(i)}' for i in range(level1_parm_count)]
    l2_parms = [f'L2P_{str(i)}' for i in range(level2_parm_count)]
    return {l1p: {l2p: pd.Series([sin(j/(parm_sample_count*.01)) * int(l2p.split('_')[-1])
                                  for j in range(parm_sample_count)],
                                 index=pd.to_timedelta(range(parm_sample_count), unit='s'))
                  for l2p in l2_parms}
            for l1p in l1_parms}


if __name__ == '__main__':

    data_source = 'generic'       # zoomable!
    # data_source = 'real'          # not zoomable!

    data = read_json('data.txt') if data_source == 'real' else create_generic_data(3, 3, 100000)
    myplot = MultiPlot(data)
    myplot.show()
```

Here's my interpreter setup:
[pipfreeze.txt](https://github.com/holoviz/holoviews/files/10198058/pipfreeze.txt)

Thanks!



Discussion:

Resolving patch:
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -595,17 +595,14 @@ class RangeXYCallback(Callback):
     }
 
     _js_on_event = """
+    if (this._updating)
+        return
     const plot = this.origin
     const plots = plot.x_range.plots.concat(plot.y_range.plots)
-    const updated = [plot]
     for (const p of plots) {
-      if (updated.indexOf(p) >= 0 || p.tags.indexOf('ranges-updating') >= 0)
-        continue
-      p.tags.push('ranges-updating')
       const event = new this.constructor(p.x_range.start, p.x_range.end, p.y_range.start, p.y_range.end)
+      event._updating = true
       p.trigger_event(event)
-      p.tags = p.tags.filter(x => x !== 'ranges-updating')
-      updated.push(p)
     }
     """
 


Test patch: