Skip to content

Commit

Permalink
Merge tools in grid and layout plots (#5640)
Browse files Browse the repository at this point in the history
Co-authored-by: Simon Høxbro Hansen <simon.hansen@me.com>
  • Loading branch information
philippjfr and hoxbro authored Apr 14, 2023
1 parent 3b61f43 commit 4d0f80b
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 19 deletions.
38 changes: 20 additions & 18 deletions holoviews/plotting/bokeh/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .links import LinkCallback
from .util import (
bokeh3, filter_toolboxes, make_axis, update_shared_sources, empty_plot,
decode_bytes, theme_attr_json, cds_column_replace, get_default
decode_bytes, theme_attr_json, cds_column_replace, get_default, merge_tools
)

if bokeh3:
Expand Down Expand Up @@ -582,11 +582,13 @@ def initialize_plot(self, ranges=None, plots=[]):
else:
passed_plots.append(None)

toolbar_location = None if bokeh3 else self.toolbar
plot = gridplot(plots[::-1], merge_tools=self.merge_tools,
plot = gridplot(plots[::-1],
merge_tools=self.merge_tools,
sizing_mode=self.sizing_mode,
toolbar_location=toolbar_location)
toolbar_location=self.toolbar)
plot = self._make_axes(plot)
if bokeh3 and hasattr(plot, "toolbar"):
plot.toolbar = merge_tools(plots)

title = self._get_title_div(self.keys[-1])
if title:
Expand Down Expand Up @@ -637,10 +639,7 @@ def _make_axes(self, plot):
if self.shared_yaxis:
x_axis.margin = (0, 0, 0, 50)
r1, r2 = r1[::-1], r2[::-1]
if bokeh3:
plot = gridplot([r1, r2], toolbar_location=None)
else:
plot = gridplot([r1, r2])
plot = gridplot([r1, r2])
elif y_axis:
models = [y_axis, plot]
if self.shared_yaxis: models = models[::-1]
Expand Down Expand Up @@ -917,15 +916,14 @@ def initialize_plot(self, plots=None, ranges=None):

if nsubplots == 1:
grid = subplots[0]
elif nsubplots == 2:
grid = gridplot([subplots], merge_tools=self.merge_tools,
toolbar_location=self.toolbar,
sizing_mode=sizing_mode)
else:
grid = [[subplots[2], None], subplots[:2]]
grid = gridplot(children=grid, merge_tools=self.merge_tools,
children = [subplots] if nsubplots == 2 else [[subplots[2], None], subplots[:2]]
grid = gridplot(children,
merge_tools=self.merge_tools,
toolbar_location=self.toolbar,
sizing_mode=sizing_mode)
if bokeh3:
grid.toolbar = merge_tools(children)
tab_plots.append((title, grid))
continue

Expand Down Expand Up @@ -958,10 +956,14 @@ def initialize_plot(self, plots=None, ranges=None):
layout_plot = Tabs(tabs=panels, sizing_mode=sizing_mode)
else:
plot_grid = filter_toolboxes(plot_grid)
layout_plot = gridplot(children=plot_grid,
toolbar_location=self.toolbar,
merge_tools=self.merge_tools,
sizing_mode=sizing_mode)
layout_plot = gridplot(
children=plot_grid,
toolbar_location=self.toolbar,
merge_tools=self.merge_tools,
sizing_mode=sizing_mode
)
if bokeh3:
layout_plot.toolbar = merge_tools(plot_grid)

title = self._get_title_div(self.keys[-1])
if title:
Expand Down
38 changes: 37 additions & 1 deletion holoviews/plotting/bokeh/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@
bokeh3 = bokeh_version >= Version("3.0")

if bokeh3:
from bokeh.layouts import group_tools
from bokeh.models.formatters import CustomJSTickFormatter
from bokeh.models import Toolbar, Tabs, GridPlot
from bokeh.models import Toolbar, Tabs, GridPlot, SaveTool, CopyTool, ExamineTool, FullscreenTool, LayoutDOM
from bokeh.plotting import figure
class WidgetBox: pass # Does not exist in Bokeh 3

Expand Down Expand Up @@ -374,6 +375,39 @@ def compute_layout_properties(
return aspect_info, dimension_info


def merge_tools(plot_grid, disambiguation_properties=None):
"""
Merges tools defined on a grid of plots into a single toolbar.
All tools of the same type are merged unless they define one
of the disambiguation properties. By default `name`, `icon`, `tags`
and `description` can be used to prevent tools from being merged.
"""
tools = []
for row in plot_grid:
for item in row:
if isinstance(item, LayoutDOM):
for p in item.select(dict(type=Plot)):
tools.extend(p.toolbar.tools)
if isinstance(item, GridPlot):
item.toolbar_location = None

def merge(tool, group):
if issubclass(tool, (SaveTool, CopyTool, ExamineTool, FullscreenTool)):
return tool()
else:
return None

if not disambiguation_properties:
disambiguation_properties = {'name', 'icon', 'tags', 'description'}

ignore = set()
for tool in tools:
for p in tool.properties_with_values():
if p not in disambiguation_properties:
ignore.add(p)

return Toolbar(tools=group_tools(tools, merge=merge, ignore=ignore) if merge_tools else tools)

@contextmanager
def silence_warnings(*warnings):
"""
Expand Down Expand Up @@ -575,6 +609,8 @@ def filter_toolboxes(plots):
"""
if isinstance(plots, list):
plots = [filter_toolboxes(plot) for plot in plots]
elif hasattr(plots, 'toolbar'):
plots.toolbar_location = None
elif hasattr(plots, 'children'):
plots.children = [filter_toolboxes(child) for child in plots.children
if not isinstance(child, Toolbar)]
Expand Down

0 comments on commit 4d0f80b

Please sign in to comment.