holoviz / holoviews

With Holoviews, your data visualizes itself.
https://holoviews.org
BSD 3-Clause "New" or "Revised" License
2.67k stars 399 forks source link

Single, unified colorbar for layout #4237

Open ahuang11 opened 4 years ago

ahuang11 commented 4 years ago

Is there a way to set a unified colorbar for a layout?

import xarray as xr
import hvplot.xarray
ds = xr.tutorial.open_dataset('air_temperature').isel(time=[0, 4, 7, 8])
ds.hvplot('lon', 'lat').layout().cols(2)
jbednar commented 4 years ago

That code doesn't run for me on master with xarray 0.13 or 0.15; I get AttributeError: 'DataFrame' object has no attribute 'data_vars'.

ahuang11 commented 4 years ago

I think it works for me with latest master and xarray==0.15 image

poplarShift commented 4 years ago

Does this not "just" amount to the fact that bokeh does not permit creating legends/colorbars on its own, i.e. not attached to a particular axis?

jbednar commented 4 years ago

Even so, you can still disable the colorbar on all but one plot, which works but is clunky:

import xarray as xr
import hvplot.xarray
ds = xr.tutorial.open_dataset('air_temperature').isel(time=[0, 4, 7, 8])
l = ds.hvplot('lon', 'lat', colorbar=False).layout()
l.values()[-1].opts(colorbar=True)
l.cols(2)

image

(I got the code to run locally by deleting my ~/.xarray_tutorial_data/air_temperature.nc file, which must have been cached from a long-ago xarray version; the current version xarray downloads works fine.)

It would be nice to have a less-clunky way to do this, and also to ensure that all plots actually do respect that single shared colorbar.

poplarShift commented 4 years ago

Yes, though it only gets really clunky if you want a colorbar that stretches over, let's say, the entire height of the subplot grid, instead of being attached to only one of the subplots. And I'm not sure it's even doable in current bokeh without creating a fifth panel and then hiding the axes.

ahuang11 commented 3 weeks ago

To answer this question https://discourse.holoviz.org/t/shared-colorbar-for-all-heatmap-subplots/448/2

With Panel, it's possible with a lot of opts :)

import xarray as xr
import hvplot.xarray
import panel as pn
ds = xr.tutorial.open_dataset('air_temperature').isel(time=[0, 4, 7, 8])
l = ds.hvplot('lon', 'lat', colorbar=False).layout()
plots = l.cols(2)

shared_colorbar = l.values()[0].clone().opts(
    colorbar=True,
    frame_width=0,
    frame_height=500,
    show_frame=False,
    shared_axes=False,
    xaxis=None,
    yaxis=None,
    toolbar=None,
    colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"},
)
# colorbar_opts: background_fill_alpha, background_fill_color, bar_line_alpha, bar_line_cap, bar_line_color, bar_line_dash, bar_line_dash_offset, bar_line_join, bar_line_width, border_line_alpha, border_line_cap, border_line_color, border_line_dash, border_line_dash_offset, border_line_join, border_line_width, color_mapper, context_menu, coordinates, display_high, display_low, elements, formatter, group, height, js_event_callbacks, js_property_callbacks, label_standoff, level, location, major_label_overrides, major_label_policy, major_label_text_align, major_label_text_alpha, major_label_text_baseline, major_label_text_color, major_label_text_font, major_label_text_font_size, major_label_text_font_style, major_label_text_line_height, major_label_text_outline_color, major_tick_in, major_tick_line_alpha, major_tick_line_cap, major_tick_line_color, major_tick_line_dash, major_tick_line_dash_offset, major_tick_line_join, major_tick_line_width, major_tick_out, margin, minor_tick_in, minor_tick_line_alpha, minor_tick_line_cap, minor_tick_line_color, minor_tick_line_dash, minor_tick_line_dash_offset, minor_tick_line_join, minor_tick_line_width, minor_tick_out, name, orientation, padding, propagate_hover, renderers, scale_alpha, subscribed_events, syncable, tags, ticker, title, title_standoff, title_text_align, title_text_alpha, title_text_baseline, title_text_color, title_text_font, title_text_font_size, title_text_font_style, title_text_line_height, title_text_outline_color, visible, width, x_range_name or y_range_name

pn.Row(plots, shared_colorbar, align="center")
image
iuryt commented 3 weeks ago

To answer this question https://discourse.holoviz.org/t/shared-colorbar-for-all-heatmap-subplots/448/2

With Panel, it's possible with a lot of opts :)

import xarray as xr
import hvplot.xarray
import panel as pn
ds = xr.tutorial.open_dataset('air_temperature').isel(time=[0, 4, 7, 8])
l = ds.hvplot('lon', 'lat', colorbar=False).layout()
plots = l.cols(2)

shared_colorbar = l.values()[0].clone().opts(
    colorbar=True,
    frame_width=0,
    frame_height=500,
    show_frame=False,
    shared_axes=False,
    xaxis=None,
    yaxis=None,
    toolbar=None,
    colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"},
)
# colorbar_opts: background_fill_alpha, background_fill_color, bar_line_alpha, bar_line_cap, bar_line_color, bar_line_dash, bar_line_dash_offset, bar_line_join, bar_line_width, border_line_alpha, border_line_cap, border_line_color, border_line_dash, border_line_dash_offset, border_line_join, border_line_width, color_mapper, context_menu, coordinates, display_high, display_low, elements, formatter, group, height, js_event_callbacks, js_property_callbacks, label_standoff, level, location, major_label_overrides, major_label_policy, major_label_text_align, major_label_text_alpha, major_label_text_baseline, major_label_text_color, major_label_text_font, major_label_text_font_size, major_label_text_font_style, major_label_text_line_height, major_label_text_outline_color, major_tick_in, major_tick_line_alpha, major_tick_line_cap, major_tick_line_color, major_tick_line_dash, major_tick_line_dash_offset, major_tick_line_join, major_tick_line_width, major_tick_out, margin, minor_tick_in, minor_tick_line_alpha, minor_tick_line_cap, minor_tick_line_color, minor_tick_line_dash, minor_tick_line_dash_offset, minor_tick_line_join, minor_tick_line_width, minor_tick_out, name, orientation, padding, propagate_hover, renderers, scale_alpha, subscribed_events, syncable, tags, ticker, title, title_standoff, title_text_align, title_text_alpha, title_text_baseline, title_text_color, title_text_font, title_text_font_size, title_text_font_style, title_text_line_height, title_text_outline_color, visible, width, x_range_name or y_range_name

pn.Row(plots, shared_colorbar, align="center")
image

What if we have tiles? Is there a way to create a panel for the colorbar instead of copying from the layout?

ahuang11 commented 3 weeks ago

What tiles? Can you share an example?

iuryt commented 3 weeks ago

Sorry..

I meant

import xarray as xr
import hvplot.xarray
import panel as pn

ds = xr.tutorial.open_dataset('air_temperature').isel(time=[0, 4, 7, 8])
l = ds.hvplot('lon', 'lat', geo=True, tiles="EsriImagery", colorbar=False).layout()
plots = l.cols(2)
plots

creates

image

but when I do

shared_colorbar = l.values()[0].clone().opts(
    colorbar=True,
    frame_width=0,
    frame_height=500,
    show_frame=False,
    shared_axes=False,
    xaxis=None,
    yaxis=None,
    toolbar=None,
    colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"},
)

pn.Row(plots, shared_colorbar, align="center")

it returns

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 shared_colorbar = l.values()[0].clone().opts(
      2     colorbar=True,
      3     frame_width=0,
      4     frame_height=500,
      5     show_frame=False,
      6     shared_axes=False,
      7     xaxis=None,
      8     yaxis=None,
      9     toolbar=None,
     10     colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"},
     11 )
     12 # colorbar_opts: background_fill_alpha, background_fill_color, bar_line_alpha, bar_line_cap, bar_line_color, bar_line_dash, bar_line_dash_offset, bar_line_join, bar_line_width, border_line_alpha, border_line_cap, border_line_color, border_line_dash, border_line_dash_offset, border_line_join, border_line_width, color_mapper, context_menu, coordinates, display_high, display_low, elements, formatter, group, height, js_event_callbacks, js_property_callbacks, label_standoff, level, location, major_label_overrides, major_label_policy, major_label_text_align, major_label_text_alpha, major_label_text_baseline, major_label_text_color, major_label_text_font, major_label_text_font_size, major_label_text_font_style, major_label_text_line_height, major_label_text_outline_color, major_tick_in, major_tick_line_alpha, major_tick_line_cap, major_tick_line_color, major_tick_line_dash, major_tick_line_dash_offset, major_tick_line_join, major_tick_line_width, major_tick_out, margin, minor_tick_in, minor_tick_line_alpha, minor_tick_line_cap, minor_tick_line_color, minor_tick_line_dash, minor_tick_line_dash_offset, minor_tick_line_join, minor_tick_line_width, minor_tick_out, name, orientation, padding, propagate_hover, renderers, scale_alpha, subscribed_events, syncable, tags, ticker, title, title_standoff, title_text_align, title_text_alpha, title_text_baseline, title_text_color, title_text_font, title_text_font_size, title_text_font_style, title_text_line_height, title_text_outline_color, visible, width, x_range_name or y_range_name
     14 pn.Row(plots, shared_colorbar, align="center")

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/accessors.py:35, in AccessorPipelineMeta.pipelined.<locals>.pipelined_call(*args, **kwargs)
     31 inst = args[0]
     33 if not hasattr(inst._obj, '_pipeline'):
     34     # Wrapped object doesn't support the pipeline property
---> 35     return __call__(*args, **kwargs)
     37 inst_pipeline = copy.copy(inst._obj. _pipeline)
     38 in_method = inst._obj._in_method

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/accessors.py:568, in Opts.__call__(self, *args, **kwargs)
    562         msg = ("Calling the .opts method with options broken down by options "
    563                "group (i.e. separate plot, style and norm groups) is deprecated. "
    564                "Use the .options method converting to the simplified format "
    565                "instead or use hv.opts.apply_groups for backward compatibility.")
    566         param.main.param.warning(msg)
--> 568 return self._dispatch_opts( *args, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/accessors.py:572, in Opts._dispatch_opts(self, *args, **kwargs)
    570 def _dispatch_opts(self, *args, **kwargs):
    571     if self._mode is None:
--> 572         return self._base_opts(*args, **kwargs)
    573     elif self._mode == 'holomap':
    574         return self._holomap_opts(*args, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/accessors.py:651, in Opts._base_opts(self, *args, **kwargs)
    648     return opts.apply_groups(self._obj, **dict(kwargs, **new_kwargs))
    650 kwargs['clone'] = False if clone is None else clone
--> 651 return self._obj.options(*new_args, **kwargs)

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/core/dimension.py:1276, in Dimensioned.options(self, clone, *args, **kwargs)
   1274     expanded_backends = opts._expand_by_backend(options, backend)
   1275 else:
-> 1276     expanded_backends = [(backend, opts._expand_options(options, backend))]
   1278 obj = self
   1279 for backend, expanded in expanded_backends:

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/util/__init__.py:361, in opts._expand_options(cls, options, backend)
    355         else:
    356             valid_options = sorted({
    357                 keyword
    358                 for group_opts in obj_options.groups.values()
    359                 for keyword in group_opts.allowed_keywords
    360             })
--> 361             cls._options_error(opt, objtype, backend, valid_options)
    362 return expanded

File ~/miniforge3/envs/coringa/lib/python3.9/site-packages/holoviews/util/__init__.py:404, in opts._options_error(cls, opt, objtype, backend, valid_options)
    401     return
    403 if matches:
--> 404     raise ValueError('Unexpected option %r for %s type '
    405                      'across all extensions. Similar options '
    406                      'for current extension (%r) are: %s.' %
    407                      (opt, objtype, current_backend, matches))
    408 else:
    409     raise ValueError('Unexpected option %r for %s type '
    410                      'across all extensions. No similar options '
    411                      'found.' % (opt, objtype))

ValueError: Unexpected option 'colorbar' for Overlay type across all extensions. Similar options for current extension ('bokeh') are: ['bgcolor', 'toolbar'].
ahuang11 commented 3 weeks ago

Maybe

shared_colorbar = l.values()[0].clone().opts(
    "Image",
    colorbar=True,
    frame_width=0,
    frame_height=500,
    show_frame=False,
    shared_axes=False,
    xaxis=None,
    yaxis=None,
    toolbar=None,
    colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"},
)

pn.Row(plots, shared_colorbar, align="center")
iuryt commented 3 weeks ago

shared_colorbar = l.values()[0].clone().opts( "Image", colorbar=True, frame_width=0, frame_height=500, show_frame=False, shared_axes=False, xaxis=None, yaxis=None, toolbar=None, colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"}, )

pn.Row(plots, shared_colorbar, align="center")

It returned something like this

image

😓

ahuang11 commented 3 weeks ago
import xarray as xr
import hvplot.xarray
import panel as pn

ds = xr.tutorial.open_dataset('air_temperature').isel(time=[0, 4, 7, 8])
l = ds.hvplot('lon', 'lat', geo=True, tiles="EsriImagery", colorbar=False).layout()
plots = l.cols(2)
shared_colorbar = plots.values()[0].get(1).clone().opts(
    "Image",
    colorbar=True,
    frame_width=1,
    frame_height=550,
    show_frame=False,
    shared_axes=False,
    xaxis=None,
    yaxis=None,
    toolbar=None,
    colorbar_opts={"width": 50, "height": 400, "title": "Temperature (°C)"},
)

pn.Row(plots, shared_colorbar, align="center", styles={"background-color": "white"})
image

How I approached it:

Print out the plots

image

Get the image

image

Tweak the opts, specifically width