I have inherited some Python code that uses Bokeh from a developer who has left. I don’t fully understand what it is doing.
We create a figure that we use to create a timeline. x-axis is time. There are a number of events which are drawn as rectangles which appear in a number of rows. Rows can contain several different types of event. Position and width of event represent time and duration.
There can have more than 100,000 events. Because Bokeh becomes slow when trying to draw this many events we limit the x-range to reduce the number of events that have to be drawn. Users then have to pan to see the rest of the timeline. Sometimes we have to restrict the time range quite a lot.
What we are doing now means that users see full detail of all the events but with a limited time range so they don’t get to see the big picture over the whole time range.
We have a legend on the figure with checkboxes that allows users to hide some of the event types to reduce the number that are displayed.
Some users would like to be able to hide event types they are not interested in, reducing the total number of events, and then zoom out further than we currently allow them to see more of the time range and get to see the bigger picture.
Can anyone suggest an approach to achieve this?
One theoretical approach is when a checkbox is unchecked to hide a class of event there is a callback which calculates a new maximum time range based on the number of events now visible and dynamically updates the time range of the figure. Is this possible?
Here’s a picture to help. The time range is 264s but we’re restricting it to 36s to get decent performance. What looks like solid bars are actually 1000s of separate events.

It's extremely difficult to provide code because the Bokeh calls are dotted throughout our code and I did not write it. This may or may not be helpful:
x_range= Range1d(start=self._time_range_params.range_start, end=self._time_range_params.range_end,
bounds=(self._time_range_params.bound_lower, self._time_range_params.bound_upper),
min_interval=self._time_range_params.zoom_min_interval,
max_interval=self._time_range_params.zoom_max_interval)
self._figure = figure(plot_width=self.DEFAULT_WIDTH, plot_height=plot_height, sizing_mode=self.SIZING_MODE,
y_range=y_range, x_range=x_range, name=timeline_name, lod_factor=self.LOD_FACTOR,
lod_threshold=self.LOD_THRESHOLD, lod_timeout=self.LOD_TIMEOUT,
tags=create_figure_tags(self._x_axis_data, bounds=x_range.bounds),
css_classes=[self.CSS_CLASS_NAME])
for event_type in self._event_types:
r_status, r_event = self._plot_event_type(status_filter, event_type)
legend_items.append(LegendItem(label=event_type, renderers=[r_status, r_event]))
status_renderers.append(r_status)
event_renderers.append(r_event)
# add toggle all option
legend_items.append(self._create_toggle_all_legend_item([*status_renderers, *event_renderers]))
self._add_legend(legend_items, label='All', name=f'{self._figure.name}Legend0',
title='Click to hide/show events', visible=True)
# store the event renderers to allow hit testing e.g. for hover tool, to avoid duplicates from status renderers
self._event_renderers = event_renderers
range_filter = DynamicRangeFilter(self._figure, self._data_source, self._time_range_params,
self._view_props.timestamp_format, TimelineItemHandler.PROP_COLUMNS, self._view_props.log_name,
update_on_linked_selections=update_on_linked_selections)
def _plot_event_type(self, status_filter: CustomJSFilter, event_type: str) -> Tuple[Renderer, Renderer]:
"""Plot the statuses and events for a single event type on the timeline.
Returns:
tuple of [status_renderer, event_renderer]
"""
# create filtered views to select events of just this type from the column data source
# (filters are computed in the browser by Bokeh)
event_filter = GroupFilter(column_name='EventType', group=event_type)
# plot taller boxes in status colour underneath boxes in event colour
# this creates a top-bottom border for events with the status colour when rendered
# get events of just this type which have a known status
event_status_view = CDSView(source=self._data_source, filters=[status_filter, event_filter])
r_status = self._figure.rect(x='TimeMid', y='EventGroup', width='Duration',
height=self.EVENT_STATUS_HEIGHT,
line_color='StatusColor', fill_color='StatusColor', alpha=1,
source=self._data_source, view=event_status_view, dilate=True)
event_view = CDSView(source=self._data_source, filters=[event_filter])
r_event = self._figure.rect(x='TimeMid', y='EventGroup', width='Duration', height=self.EVENT_HEIGHT,
line_color='EventColor', fill_color='EventColor', alpha=1,
source=self._data_source, view=event_view, dilate=True)
return r_status, r_event