How do I achieve consistent appearance in differen

2019-08-09 03:42发布

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.

enter image description here

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

1条回答
ら.Afraid
2楼-- · 2019-08-09 04:39

I don't think you're missing anything in terms of actual software support; what you're missing is that HoloViews in no way promises to make it simple to make plots from different backends to look the same. The plots are meant to show the same data in roughly the same way, but the backends each work in different ways, and some of those differences are in fact reasons to choose that particular backend over another.

There are certainly ways that HoloViews could map from an abstract notion of styling into the details of how that's done in different backends, but that's surprisingly tricky. And very few users ask for that; most pick their favorite backend and just use it, and would rather we spend our limited development time working on other features.

That said, if the backends can produce similar plots, you should be able to work out settings for use with HoloViews that will generate them in matching form. To do this, you'd work out the settings one backend at a time, then apply them per backend. E.g. .opts(line_width=3, backend='bokeh').opts(linewidth=4.5, backend='matplotlib'), with the appropriate option being used when that object is displayed by each backend. Here the two options differ only by one character in their names, but they work very differently for such a seemingly simple concept of line width: matplotlib accepts a width in "points" (which depends on dpi and knowing the absolute size in inches), while bokeh accepts pixels in screen space. They are both widths, but there's not necessarily any direct way to compare the two values, as it depends on separate settings you may have done for dpi and fig_size. You should be able to get it to look similar with enough effort, but trying to achieve that across all plots for all time is a massive task that would need some separate funding and developers to achieve! Still, it's already much easier to do that in HoloViews than it would be to completely rewrite a plot between Matplotlib and Bokeh natively, so HoloViews is still helping a good bit, just not solving everything for you...

查看更多
登录 后发表回答