I'm mystified by how to use HoloViews styles to customize plots and achieve a consistent appearance across backends. HoloViews is billed as a package that provides an abstraction layer to several backends, notably Bokeh and Matplotlib, but I'm completely failing in my attempts to get plots generated using these backends to look the same. Settings in one backend are ignored by another, and each backend has many (most) formatting options missing, so that it is necessary to break through the abstraction to lower level calls directly the the backends.
I suspect I'm just missing something or have failed to discover the appropriate documentation.
The code below for example (using settings that don't attempt to produce the same appearance, but expose some of the issues) results in Matplotlib figures (right) that
- ignore the attempt to achieve a uniform appearance for scatter plot point color,
- ignore the attempt to override the color of histogram bars,
- have marginal histograms with axis labels that are explicitly removed in the Bokeh versions (left),
- have marginal histograms that are not framed and lack the vertical axis present in the Bokeh version,
- have no control or customizations over the styling of axes, and
- have additional subplot labels not present on the Bokeh plot.
In addition, there are many further customizations to both backend's plots (gridlines, frame color, for example) that I can't find settings for.
How do I set styles in HoloViews to achieve full and consistent control over plots produced by Bokeh and Matplotlib?
import numpy as np
import pandas as pd
import holoviews as hv
hv.extension('bokeh', 'matplotlib')
ds = hv.Dataset({'x': np.random.randn(100), 'y1': np.random.randn(100), 'y2': np.random.randn(100), 'y3': np.random.randn(100)},
['x'],['y1', 'y2', 'y3'])
def mpl_style_hook(plot, element):
# Settings required here are neither complete, nor do they correspond directly to the backend's naming
# Where is the correspondence between handles and the backend's names documented?
pass
def bok_style_hook(plot, element):
# Such a small set of abstractions is provided, it is almost always necessary to resort to hooks
plot.state.title.align = "center"
plot.handles['xaxis'].axis_label_text_color = 'red'
plot.handles['yaxis'].axis_label_text_color = 'green'
plot.handles['xaxis'].axis_label_text_font_style = "normal"
plot.handles['yaxis'].axis_label_text_font_style = "normal"
# Attempt to set options that apply to both backends; but ignored by Matplotlib
hv.opts.defaults(hv.opts.Scatter(color='green'), hv.opts.Histogram(fill_color='yellow'))
# Explictily set backend to avoid warnings (`backend=` isn't sufficient)
hv.Store.current_backend = 'bokeh'
hv.opts.defaults(
hv.opts.Scatter(line_color='orange', size=6, fill_alpha=1.0, hooks=[bok_style_hook]),
hv.opts.Histogram(fill_color='cyan', fill_alpha=0.9, line_width=1, line_color='gray', hooks=[bok_style_hook]),
backend='bokeh')
hv.Store.current_backend = 'matplotlib'
hv.opts.defaults(
hv.opts.Scatter(hooks=[mpl_style_hook]),
# Histogram color ignored
hv.opts.Histogram(color='orange', hooks=[mpl_style_hook]),
backend='matplotlib')
hv.Store.current_backend = 'bokeh'
s1 = hv.Scatter(ds, 'x', 'y1').opts(hv.opts.Scatter(labelled=[None, 'y'])).hist(num_bins=51, dimension=['x','y1'])
s2 = hv.Scatter(ds, 'x', 'y2').opts(hv.opts.Scatter(labelled=[None, 'y'])).hist(num_bins=51, dimension='y2')
s3 = hv.Scatter(ds, 'x', 'y3').hist(num_bins=51, dimension='y3')
p = (s1 + s2 + s3).opts(hv.opts.Histogram(labelled=[None, None]), hv.opts.Layout(shared_axes=True)).cols(1)
hv.save(p, '_testHV.html', backend='bokeh')
hv.save(p, '_testHV.png', backend='matplotlib')
p