Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Layout/Grid titles in bokeh #1017

Merged
merged 5 commits into from
Dec 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 63 additions & 5 deletions holoviews/plotting/bokeh/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

import param

from bokeh.models import ColumnDataSource, VBox, HBox, GridPlot as BokehGridPlot
from bokeh.models import (ColumnDataSource, VBox, HBox, Column,
GridPlot as BokehGridPlot, Div)
from bokeh.models.widgets import Panel, Tabs

from ...core import (OrderedDict, CompositeOverlay, Store, Layout, GridMatrix,
Expand Down Expand Up @@ -153,7 +154,48 @@ def sync_sources(self):



class GridPlot(BokehPlot, GenericCompositePlot):
class CompositePlot(BokehPlot):
"""
CompositePlot is an abstract baseclass for plot types that draw
render multiple axes. It implements methods to add an overall title
to such a plot.
"""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have a docstring explaining what this base class is for. Right now, everything CompositePlot does seems to be only needed by LayoutPlot. Any ideas what CompositePlot could be used for that isn't LayoutPlot?

Copy link
Member Author

@philippjfr philippjfr Dec 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GridPlot, just below, but yes I'll add a docstring.

fontsize = param.Parameter(default={'title': '16pt'}, allow_None=True, doc="""
Specifies various fontsizes of the displayed text.

Finer control is available by supplying a dictionary where any
unmentioned keys reverts to the default sizes, e.g:

{'title': '15pt'}""")

_title_template = "<span style='font-size: {fontsize}'><b>{title}</b></font>"

def _get_title(self, key):
title_div = None
title = self._format_title(key) if self.show_title else ''
if title:
fontsize = self._fontsize('title')
title_tags = self._title_template.format(title=title,
**fontsize)
if 'title' in self.handles:
title_div = self.handles['title']
else:
title_div = Div()
title_div.text = title_tags
return title_div

@property
def current_handles(self):
"""
Should return a list of plot objects that have changed and
should be updated.
"""
return [self.handles['title']]



class GridPlot(CompositePlot, GenericCompositePlot):
"""
Plot a group of elements in a grid layout based on a GridSpace element
object.
Expand Down Expand Up @@ -256,9 +298,14 @@ def initialize_plot(self, ranges=None, plots=[]):
plots[r].append(None)
passed_plots.append(None)
if bokeh_version < '0.12':
self.handles['plot'] = BokehGridPlot(children=plots[::-1])
plot = BokehGridPlot(children=plots[::-1])
else:
self.handles['plot'] = gridplot(plots[::-1])
plot = gridplot(plots[::-1])
title = self._get_title(self.keys[-1])
if title:
self.handles['title'] = title
plot = Column(title, plot)
self.handles['plot'] = plot
self.handles['plots'] = plots
if self.shared_datasource:
self.sync_sources()
Expand All @@ -278,10 +325,13 @@ def update_frame(self, key, ranges=None):
subplot = self.subplots.get(coord, None)
if subplot is not None:
subplot.update_frame(key, ranges)
title = self._get_title(key)
if title:
self.handles['title']



class LayoutPlot(BokehPlot, GenericLayoutPlot):
class LayoutPlot(CompositePlot, GenericLayoutPlot):

shared_axes = param.Boolean(default=True, doc="""
Whether axes should be shared across plots""")
Expand Down Expand Up @@ -505,6 +555,10 @@ def initialize_plot(self, ranges=None):
else:
layout_plot = BokehGridPlot(children=plots)

title = self._get_title(self.keys[-1])
if title:
self.handles['title'] = title
layout_plot = Column(title, layout_plot)
self.handles['plot'] = layout_plot
self.handles['plots'] = plots
if self.shared_datasource:
Expand All @@ -526,6 +580,10 @@ def update_frame(self, key, ranges=None):
subplot = self.subplots.get((r, c), None)
if subplot is not None:
subplot.update_frame(key, ranges)
title = self._get_title(key)
if title:
self.handles['title'] = title



class AdjointLayoutPlot(BokehPlot):
Expand Down
60 changes: 59 additions & 1 deletion tests/testplotinstantiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import param
import numpy as np
from holoviews import (Dimension, Overlay, DynamicMap, Store,
NdOverlay, GridSpace)
NdOverlay, GridSpace, HoloMap, Layout)
from holoviews.element import (Curve, Scatter, Image, VLine, Points,
HeatMap, QuadMesh, Spikes, ErrorBars,
Scatter3D, Path, Polygons, Bars)
Expand All @@ -32,6 +32,7 @@
import holoviews.plotting.bokeh
bokeh_renderer = Store.renderers['bokeh']
from holoviews.plotting.bokeh.callbacks import Callback
from bokeh.models import Div
from bokeh.models.mappers import LinearColorMapper, LogColorMapper
from bokeh.models.tools import HoverTool
except:
Expand Down Expand Up @@ -282,6 +283,63 @@ def test_image_boolean_array(self):
self.assertEqual(source.data['image'][0],
np.array([[0, 1], [1, 0]]))

def test_layout_title(self):
hmap1 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
hmap2 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
plot = bokeh_renderer.get_plot(hmap1+hmap2)
title = plot.handles['title']
self.assertIsInstance(title, Div)
text = "<span style='font-size: 16pt'><b>Default: 0</b></font>"
self.assertEqual(title.text, text)

def test_layout_title_fontsize(self):
hmap1 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
hmap2 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
layout = Layout([hmap1, hmap2])(plot=dict(fontsize={'title': '12pt'}))
plot = bokeh_renderer.get_plot(layout)
title = plot.handles['title']
self.assertIsInstance(title, Div)
text = "<span style='font-size: 12pt'><b>Default: 0</b></font>"
self.assertEqual(title.text, text)

def test_layout_title_show_title_false(self):
hmap1 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
hmap2 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
layout = Layout([hmap1, hmap2])(plot=dict(show_title=False))
plot = bokeh_renderer.get_plot(layout)
self.assertTrue('title' not in plot.handles)

def test_layout_title_update(self):
hmap1 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
hmap2 = HoloMap({a: Image(np.random.rand(10,10)) for a in range(3)})
plot = bokeh_renderer.get_plot(hmap1+hmap2)
plot.update(1)
title = plot.handles['title']
self.assertIsInstance(title, Div)
text = "<span style='font-size: 16pt'><b>Default: 1</b></font>"
self.assertEqual(title.text, text)

def test_grid_title(self):
grid = GridSpace({(i, j): HoloMap({a: Image(np.random.rand(10,10))
for a in range(3)}, kdims=['X'])
for i in range(2) for j in range(3)})
plot = bokeh_renderer.get_plot(grid)
title = plot.handles['title']
self.assertIsInstance(title, Div)
text = "<span style='font-size: 16pt'><b>X: 0</b></font>"
self.assertEqual(title.text, text)

def test_grid_title_update(self):
grid = GridSpace({(i, j): HoloMap({a: Image(np.random.rand(10,10))
for a in range(3)}, kdims=['X'])
for i in range(2) for j in range(3)})
plot = bokeh_renderer.get_plot(grid)
plot.update(1)
title = plot.handles['title']
self.assertIsInstance(title, Div)
text = "<span style='font-size: 16pt'><b>X: 1</b></font>"
self.assertEqual(title.text, text)

def test_points_non_numeric_size_warning(self):
data = (np.arange(10), np.arange(10), list(map(chr, range(94,104))))
points = Points(data, vdims=['z'])(plot=dict(size_index=2))
Expand Down