Save hvplot figure with transparent background without margins nor axes

Hi

I’m using the following code to save an hvplot with transparent background

feat_prep=rivers.hvplot(width=600,height=500,color='red')*\
    roads.hvplot(width=600,height=500,color='yellow')*\
    borders.hvplot(width=600,height=500,color='orange')*\
    cities_gdf.hvplot(width=600,height=500,color='magenta',hover=True)*\
    cities_gdf.hvplot.labels(x='lon',y='lat',text='city',width=600,height=500,text_baseline='bottom',hover=False,fontsize=15,text_color='white')
feat_prep.opts(bgcolor='rgba(0,0,0,0)')
hv.save(feat_prep,'/f/work/julich/dg-rr/analytcis/plant_available_water/features.png',backend='bokeh')

The rivers, roads, borders, and cities are read from shape folders.
The problem is that the saved figure does not have a transparent background. However, when I click the save button on the shown “feat_prep” in Jupyter notebook, then the saved figure has a transparent background. I wonder why the “hv.save” command can’t keep the transparent background?

Also, the resulting figure has axes area. The axes area is also shown in the saved figure. So, when I try to overlay the saved figure (with transparent background) on another plot, the axes area doesn’t show the parts below it in the lower layer. I wonder how to save the hvplot without the axes area (just the transparent background with the rivers, roads, borders, and cities)?

Regards

1 Like

Maybe:
hv.Curve([0, 1, 2]).opts(xaxis=None, yaxis=None, toolbar=None, show_frame=False)

1 Like

Thank you very much. This helped with removing the axes, but I think the marginal areas are still there (even if the axes are not shown).

I wonder if there is a programmatic way to save the figure with transparent background, such that I don’t have to click the “save” button on the bokeh toolbar?

Regards

Maybe use matplotlib backend and set the color to ‘none’, or use bokeh’s

background_fill_alpha=0

Thank you. I tried it with “backend=‘bokeh’”, but the saved figure still does not have transparent background.
I tried to change the backend to matplotlib, but it produced the following error:
“ValueError: cannot convert float NaN to integer”

The complete error message is:
"
WARNING:param.MPLRenderer196839: Setting non-parameter attribute background_fill_alpha=0 using a mechanism intended only for parameters
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values
posx and posy should be finite values

ValueError Traceback (most recent call last)
/tmp/ipykernel_1108029/746930965.py in
5 cities_gdf.hvplot.labels(x=‘lon’,y=‘lat’,text=‘city’,width=600,height=500,text_baseline=‘bottom’,hover=False,fontsize=15,text_color=‘white’)
6 feat_prep=feat_prep.opts(bgcolor=‘rgba(0,0,0,0)’,xaxis=None, yaxis=None, toolbar=None, show_frame=False)
----> 7 hv.save(feat_prep,filename=’/f/work/julich/dg-rr/analytcis/plant_available_water/features.png’,backend=‘matplotlib’,background_fill_alpha=0) # In case features are needed in the future

~/.local/lib/python3.8/site-packages/holoviews/util/init.py in save(obj, filename, fmt, backend, resources, toolbar, title, **kwargs)
818 if formats[-1] in supported:
819 filename = ‘.’.join(formats[:-1])
→ 820 return renderer_obj.save(obj, filename, fmt=fmt, resources=resources,
821 title=title)
822

~/.local/lib/python3.8/site-packages/holoviews/plotting/renderer.py in save(self_or_cls, obj, basename, fmt, key, info, options, resources, title, **kwargs)
625 return
626
→ 627 rendered = self_or_cls(plot, fmt)
628 if rendered is None: return
629 (data, info) = rendered

~/.local/lib/python3.8/site-packages/holoviews/plotting/renderer.py in call(self, obj, fmt, **kwargs)
199 return self.static_html(plot), info
200 else:
→ 201 data = self._figure_data(plot, fmt, **kwargs)
202 data = self._apply_post_render_hooks(data, obj, fmt)
203 return data, info

~/.local/lib/python3.8/site-packages/holoviews/plotting/mpl/renderer.py in _figure_data(self, plot, fmt, bbox_inches, as_script, **kwargs)
173 pass
174 bytes_io = BytesIO()
→ 175 fig.canvas.print_figure(bytes_io, **kw)
176 data = bytes_io.getvalue()
177

~/miniconda3/lib/python3.8/site-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2317 # force the figure dpi to 72), so we need to set it again here.
2318 with cbook._setattr_cm(self.figure, dpi=dpi):
→ 2319 result = print_method(
2320 filename,
2321 facecolor=facecolor,

~/miniconda3/lib/python3.8/site-packages/matplotlib/backend_bases.py in wrapper(*args, **kwargs)
1646 kwargs.pop(arg)
1647
→ 1648 return func(*args, **kwargs)
1649
1650 return wrapper

~/miniconda3/lib/python3.8/site-packages/matplotlib/_api/deprecation.py in wrapper(*inner_args, **inner_kwargs)
410 else deprecation_addendum,
411 **kwargs)
→ 412 return func(*inner_args, **inner_kwargs)
413
414 DECORATORS[wrapper] = decorator

~/miniconda3/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py in print_png(self, filename_or_obj, metadata, pil_kwargs, *args)
538 metadata, including the default ‘Software’ key.
539 “”"
→ 540 FigureCanvasAgg.draw(self)
541 mpl.image.imsave(
542 filename_or_obj, self.buffer_rgba(), format=“png”, origin=“upper”,

~/miniconda3/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py in draw(self)
434 (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
435 else nullcontext()):
→ 436 self.figure.draw(self.renderer)
437 # A GUI class may be need to update a window using this draw, so
438 # don’t forget to call the superclass.

~/miniconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
71 @wraps(draw)
72 def draw_wrapper(artist, renderer, *args, **kwargs):
—> 73 result = draw(artist, renderer, *args, **kwargs)
74 if renderer._rasterizing:
75 renderer.stop_rasterizing()

~/miniconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer)
48 renderer.start_filter()
49
—> 50 return draw(artist, renderer)
51 finally:
52 if artist.get_agg_filter() is not None:

~/miniconda3/lib/python3.8/site-packages/matplotlib/figure.py in draw(self, renderer)
2808
2809 self.patch.draw(renderer)
→ 2810 mimage._draw_list_compositing_images(
2811 renderer, self, artists, self.suppressComposite)
2812

~/miniconda3/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
130 if not_composite or not has_images:
131 for a in artists:
→ 132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together

~/miniconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer)
48 renderer.start_filter()
49
—> 50 return draw(artist, renderer)
51 finally:
52 if artist.get_agg_filter() is not None:

~/miniconda3/lib/python3.8/site-packages/matplotlib/axes/_base.py in draw(self, renderer)
3080 renderer.stop_rasterizing()
3081
→ 3082 mimage._draw_list_compositing_images(
3083 renderer, self, artists, self.figure.suppressComposite)
3084

~/miniconda3/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
130 if not_composite or not has_images:
131 for a in artists:
→ 132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together

~/miniconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer)
48 renderer.start_filter()
49
—> 50 return draw(artist, renderer)
51 finally:
52 if artist.get_agg_filter() is not None:

~/miniconda3/lib/python3.8/site-packages/matplotlib/axis.py in draw(self, renderer, *args, **kwargs)
1156 renderer.open_group(name, gid=self.get_gid())
1157
→ 1158 ticks_to_draw = self._update_ticks()
1159 ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
1160 renderer)

~/miniconda3/lib/python3.8/site-packages/matplotlib/axis.py in _update_ticks(self)
1043 the axes. Return the list of ticks that will be drawn.
1044 “”"
→ 1045 major_locs = self.get_majorticklocs()
1046 major_labels = self.major.formatter.format_ticks(major_locs)
1047 major_ticks = self.get_major_ticks(len(major_locs))

~/miniconda3/lib/python3.8/site-packages/matplotlib/axis.py in get_majorticklocs(self)
1275 def get_majorticklocs(self):
1276 “”“Return this Axis’ major tick locations in data coordinates.”""
→ 1277 return self.major.locator()
1278
1279 def get_minorticklocs(self):

~/miniconda3/lib/python3.8/site-packages/matplotlib/ticker.py in call(self)
2112 def call(self):
2113 vmin, vmax = self.axis.get_view_interval()
→ 2114 return self.tick_values(vmin, vmax)
2115
2116 def tick_values(self, vmin, vmax):

~/miniconda3/lib/python3.8/site-packages/matplotlib/ticker.py in tick_values(self, vmin, vmax)
2120 vmin, vmax = mtransforms.nonsingular(
2121 vmin, vmax, expander=1e-13, tiny=1e-14)
→ 2122 locs = self._raw_ticks(vmin, vmax)
2123
2124 prune = self._prune

~/miniconda3/lib/python3.8/site-packages/matplotlib/ticker.py in _raw_ticks(self, vmin, vmax)
2059 if self._nbins == ‘auto’:
2060 if self.axis is not None:
→ 2061 nbins = np.clip(self.axis.get_tick_space(),
2062 max(1, self._min_n_ticks - 1), 9)
2063 else:

~/miniconda3/lib/python3.8/site-packages/matplotlib/axis.py in get_tick_space(self)
2268 size = self._get_tick_label_size(‘x’) * 3
2269 if size > 0:
→ 2270 return int(np.floor(length / size))
2271 else:
2272 return 2**31 - 1

ValueError: cannot convert float NaN to integer
"

Regards

Using bokeh backend, try using hooks?