Skip to content

Commit

Permalink
Add top-level save and render utilities (#3134)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored and jlstevens committed Nov 16, 2018
1 parent 517d925 commit 6f505a1
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 89 deletions.
137 changes: 89 additions & 48 deletions doc/FAQ.rst
Expand Up @@ -22,20 +22,29 @@ you can make the label 'X Label' using:
curve = hv.Curve(df, ('x_col', 'X Label'), 'y_col')
You can also change the labels later, even after the object has been
defined, by passing arguments (or an unpacked dictionary) to
.redim.label():
This is the recommended way to specify labels in a declarative way,
which will persist when applying operations to your data. You can also
change the labels later, even after the object has been defined, by
passing arguments (or an unpacked dictionary) to .redim.label():

.. code:: python
curve = hv.Curve(df, 'x_col', 'y_col')
curve = curve.redim.label(x_col='X Label', y_col='Label for Y')
To override a label for plotting it is also possible to use the
`xlabel` and `ylabel` plot options:

hv.Curve(df, 'x_col', 'y_col').options(
xlabel='X Label', ylabel='Label for Y'
)


**Q: How do I adjust the x/y/z axis bounds (matplotlib's xlim, ylim)?**

**A:** Pass an unpacked dictionary containing the kdims/vdims' names as
keys and a tuple of the bounds as values into obj.redim.range().
**A:** Pass an unpacked dictionary containing the kdims/vdims' names
as keys and a tuple of the bounds as values into
obj.redim.range().

This constrains the bounds of x_col to (0, max(x_col)).

Expand All @@ -44,47 +53,58 @@ This constrains the bounds of x_col to (0, max(x_col)).
curve = hv.Curve(df, 'x_col', 'y_col')
curve = curve.redim.range(x_col=(0, None))
This same method is applicable to adjust the range of a color bar. Here
z_col is the color bar value dimension and is bounded from 0 to 5.
As in the discussion of labels above, this approach allows you to declaratively associate ranges
with the dimensions of your data in a way that will persist even if
you apply operations to the object. This same method is applicable to
adjust the limits of the colormapping range (i.e. the `clim`).

To override the range specifically for plotting it is also possible to
set the `xlim` and `ylim` plot options:

.. code:: python
**Q: How do I control the auto-ranging/normalization of axis limits
hv.Curve(df, 'x_col', 'y_col').options(xlim=(0, None), ylim=(0, 10))
This approach allows you to customize objects easily as a final step, but note that the values won't be applied to the underlying data, and thus won't be inherited if this object is subsequently used in an operation or data selection command.


**Q: How do I control the auto-ranging/normalization of axis limits
across frames in a HoloMap or objects in a Layout?**

**A:** Where feasible, HoloViews defaults to normalizing axis ranges
across all objects that are presented together, so that they can be
compared directly. If you don't want objects that share a dimension to
across all objects that are presented together, so that they can be
compared directly. If you don't want objects that share a dimension to
be normalized together in your layout, you can change the ``axiswise``
normalization option to True, making each object be normalized
normalization option to True, making each object be normalized
independently:

.. code:: python
your_layout.options(axiswise=True)
Similarly, if you have a HoloMap composed of multiple frames in an
animation or controlled with widgets, you can make each frame be normalized
independently by changing ``framewise`` to True:
animation or controlled with widgets, you can make each frame be
normalized independently by changing ``framewise`` to True:

.. code:: python
your_holomap.options(framewise=True)
**Q: Why doesn't my DynamicMap respect the framewise=False option for
**Q: Why doesn't my DynamicMap respect the framewise=False option for
axis normalization across frames?**

**A:** Unfortunately, HoloViews has no way of knowing the axis ranges
of objects that might be returned by future calls to a DynamicMap's
callback function, and so there is no way for it to fully implement
``framewise=False`` normalization (even though such normalization
is the default in HoloViews). Thus as a special case, a DynamicMap
of objects that might be returned by future calls to a DynamicMap's
callback function, and so there is no way for it to fully implement
``framewise=False`` normalization (even though such normalization
is the default in HoloViews). Thus as a special case, a DynamicMap
(whether created specifically or as the return value of various
operations that accept a ``dynamic=True`` argument) will by default
compute its ranges *using the first frame's data only*. If that is not
the behavior you want, you either set ``framewise=True`` on it to enable
the behavior you want, you can either set ``framewise=True`` on it to enable
normalization on every frame independently, or you can manually
determine the appropriate axis range yourself and set that, e.g. with
determine the appropriate axis range yourself and set that, e.g. with
``.redim.range()`` as described above.


Expand Down Expand Up @@ -114,19 +134,59 @@ shows an example of an ``NdOverlay`` in action.

**Q: How do I export a figure?**

**A:** Create a renderer object by passing a backend (matplotlib / bokeh)
and pass the object and name of file without any suffix into the .save method.
**A:** The easiest way to save a figure is the `hv.save` utility,
which allows saving plots in different formats depending on what is
supported by the selected backend:

.. code:: python
# Using bokeh
hv.save(obj, 'plot.html', backend='bokeh')
# Using matplotlib
hv.save(obj, 'plot.svg', backend='matplotlib')
Note that the backend is optional and will default to the currently
activated backend (i.e. ``hv.Store.current_backend``).


**Q: Can I export and customize a bokeh or matplotlib figure directly?**

**A:**: Sometimes it is useful to customize a plot further using the
underlying plotting API used to render it. The `hv.render` method
returns the rendered representation of a holoviews object as bokeh or
matplotlib figure:

.. code:: python
backend = 'bokeh'
renderer = hv.renderer(backend)
renderer.save(obj, 'name_of_file')
# Using bokeh
p = hv.render(obj, backend='bokeh')
# Using matplotlib
fig = hv.render(obj, backend='matplotlib')
Note that the backend is optional and will default to the currently
activated backend (i.e. ``hv.Store.current_backend``).

If the main reason you want access to the object is to somehow customize it before it is
plotted, instead consider that it
is possible to write so called ``hooks``:

.. code:: python
curve = hv.Curve(df, 'x_col', ['y_col', 'z_col'])
curve = curve.redim.range(z_col=(0, 5))
def hook(plot, element):
# The bokeh/matplotlib figure
plot.state
# A dictionary of handles on plot subobjects, e.g. in matplotlib
# artist, axis, legend and in bokeh x_range, y_range, glyph, cds etc.
plot.handles
hv.Curve(df, 'x_col', 'y_col').options(hooks=[hook])
These hooks can modify the backend specific representation, e.g. the
matplotlib figure, before it is displayed, allowing arbitrary customizations to be
applied which are not implemented or exposed by holoviews itself.


**Q: Can I avoid generating extremely large HTML files when exporting
Expand All @@ -146,6 +206,7 @@ include:
* Displaying your data in a more highly compressed format such as
``webm``, ``mp4`` or animated ``gif``, while being aware that those
formats may introduce visible artifacts.
* When using bokeh use lower precision dtypes (e.g. float16 vs. float64)
* Replace figures with lots of data with images prerendered
by `datashade() <user_guides/Large_Data.html>`_.

Expand Down Expand Up @@ -211,26 +272,6 @@ using curly braces and unpack it.
arguments.


**Q: Can I use HoloViews without IPython/Jupyter?**

**A:** Yes! The IPython/Jupyter notebook support makes a lot of tasks easier, and
helps keep your data objects separate from the customization options,
but everything available in IPython can also be done directly from
Python. For instance, since HoloViews 1.3.0 you can render an object
directly to disk, with custom options, like this:

.. code:: python
import holoviews as hv
renderer = hv.renderer('matplotlib').instance(fig='svg', holomap='gif')
renderer.save(my_object, 'example_I', style=dict(Image={'cmap':'RdBu_r'}))
This process is described in detail in the
`Customizing Plots <user_guide/Customizing_Plots.html>`_ user guide.
Of course, notebook-specific functionality like capturing the data in
notebook cells or saving cleared notebooks is only for IPython/Jupyter.


**Q: Help! How do I find out the options for customizing the
appearance of my object?**

Expand Down Expand Up @@ -410,7 +451,7 @@ for that HoloViews object:
b.axis[0].ticker = FixedTicker(ticks=list(range(0, 10)))
h = hv.Curve([1,2,7], 'x_col', 'y_col')
h = h.options(finalize_hooks=[update_axis])
h = h.options(hooks=[update_axis])
h
Here, you've wrapped your Bokeh-API calls into a function, then
Expand Down
Expand Up @@ -108,7 +108,7 @@
"# Define some custom options for bokeh\n",
"options = hv.Store.options(backend='bokeh')\n",
"options.NdOverlay = hv.Options('plot', batched=False)\n",
"options.Curve = hv.Options('plot', show_frame=False, labelled=[], tools=['hover'], finalize_hooks=[grid_cb],\n",
"options.Curve = hv.Options('plot', show_frame=False, labelled=[], tools=['hover'], hooks=[grid_cb],\n",
" height=900, width=900, show_legend=False, xticks=[1970, 1980, 1990, 2000, 2010])\n",
"options.Curve = hv.Options('style', color=hv.Cycle(values=color_sequence), line_width=2)\n",
"\n",
Expand Down
41 changes: 41 additions & 0 deletions examples/getting_started/1-Introduction.ipynb
Expand Up @@ -349,6 +349,47 @@
"We see that slicing the HoloViews [``Points``](../reference/elements/bokeh/Points.ipynb) object in the visualization sliced the underlying data, with the structure of the table left intact. We can see that the Times Square 42nd Street station is indeed one of the subway stations surrounding our taxi dropoff hotspot. This seamless interplay and exchange between the raw data and easy-to-generate visualizations of it is crucial to how HoloViews helps you understand your data."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Saving and rendering objects\n",
"\n",
"HoloViews renders objects using a variety of backends making it easy to export plots to a variety of formats. The ``hv.save`` utility makes it trivial to save the rendered output of a HoloViews object to file, e.g. if we wanted to save the ``composition`` object from above (including the widgets) to a standalone HTML file, we would simply run:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.save(composition, 'composition.html')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If the necessary dependencies are installed this even allows saving bokeh plots to png:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.save(composition.last, 'composition.png')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"``hv.save`` defaults to the currently selected backend. As we will discover in the next section, the backend can be changed, allowing plots to be rendered to a wide range of data formats. To override the selected backend, simply supply ``backend='matplotlib'`` and you can render ``composition`` as a ``gif`` or ``mp4``)."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
Expand Up @@ -46,7 +46,7 @@
"def apply_format(plot, element):\n",
" plot.handles['plot'].columns[1].formatter=HTMLTemplateFormatter(template='<a href=\"<%= value %>\"><%= value %></a>')\n",
"\n",
"table.options(finalize_hooks=[apply_format])"
"table.options(hooks=[apply_format])"
]
}
],
Expand Down
26 changes: 22 additions & 4 deletions examples/user_guide/Exporting_and_Archiving.ipynb
Expand Up @@ -48,7 +48,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"During interactive exploration in the IPython Notebook, your results are always visible within the notebook itself, but you can explicitly request that any IPython cell is also exported to an external file on disk:"
"During interactive exploration in the Jupyter Notebook, your results are always visible within the notebook itself, but you can explicitly request that any IPython cell is also exported to an external file on disk:"
]
},
{
Expand All @@ -68,9 +68,27 @@
"source": [
"This mechanism can be used to provide a clear link between the steps for generating the figure, and the file on disk. You can now load the exported PNG image back into HoloViews, if you like, using ``hv.RGB.load_image`` although the result would be a bit confusing due to the nested axes.\n",
"\n",
"The ``fig=\"png\"`` part of the ``%%output`` magic above specified that the file should be saved in PNG format, which is useful for posting on web pages or editing in raster-based graphics programs. It also specified that if the object contained a ``HoloMap`` (which this particular one does not), it would be saved in GIF format, which supports animation. Because of the need for animation, objects containing a ``HoloMap`` are handled specially, as animation is not supported by the common PNG or SVG formats.\n",
"The ``fig=\"png\"`` part of the ``%%output`` magic above specified that the file should be saved in PNG format, which is useful for posting on web pages or editing in raster-based graphics programs. It also specified that if the object contained a ``HoloMap`` (which this particular one does not), it would be saved in GIF format, which supports animation. Because of the need for animation, objects containing a ``HoloMap`` are handled specially, as animation is not supported by the common PNG or SVG formats. \n",
"\n",
"For a publication, you will usually want to select SVG format, using ``fig=\"svg\"``, because this vector format preserves the full resolution of all text and drawing elements. SVG files can be be used in some document preparation programs directly (e.g. [LibreOffice](http://www.libreoffice.org/)), and can easily be converted using e.g. [Inkscape](https://inkscape.org) to PDF for use with PDFLaTeX or to EMF for use with Microsoft Word. They can also be edited using Inkscape or other vector drawing programs to move graphical elements around, add arbitrary text, etc., if you need to make final tweaks before using the figures in a document. You can also embed them within other SVG figures in such a drawing program, e.g. by creating a larger figure as a template that automatically incorporates multiple SVG files you have exported separately."
"It is also possible to export single files using the ``hv.save`` function, providing a pure-Python alternative. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.save(penguins, 'penguin_plot.png')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default and if the filename is a string the output format will be inferred from the file extension. Otherwise an explicit format will need to be specified using the ``fmt`` keyword argument. For ambiguous file extensions such as html it may be necessary to specify an explicit ``fmt`` to override the default, e.g. in the case of 'html' output the widgets will default to ``fmt='widgets'``, which may be changed to scrubber widgets using ``fmt='scrubber'``.\n",
"\n",
"For a publication, you will usually want to select SVG format, using ``fig=\"svg\"`` or when using ``hv.save``, ``penguin_plot.svg``, because this vector format preserves the full resolution of all text and drawing elements. SVG files can be be used in some document preparation programs directly (e.g. [LibreOffice](http://www.libreoffice.org/)), and can easily be converted using e.g. [Inkscape](https://inkscape.org) to PDF for use with PDFLaTeX or to EMF for use with Microsoft Word. They can also be edited using Inkscape or other vector drawing programs to move graphical elements around, add arbitrary text, etc., if you need to make final tweaks before using the figures in a document. You can also embed them within other SVG figures in such a drawing program, e.g. by creating a larger figure as a template that automatically incorporates multiple SVG files you have exported separately."
]
},
{
Expand Down Expand Up @@ -183,7 +201,7 @@
"\n",
" ``{dimension},{dimension},...{group}-{label},{group}-{label},...``. \n",
"\n",
"The ``{dimension}`` shows what dimension values are included anywhere in this object, if it contains any high-level ``Dimensioned`` objects like ``HoloMap``, ``NdOverlay``, and ``GridLayout``. In the last SVG image in the contents list above, which is for the ``contours`` object, there is one dimension ``Levels``, and the name shows that dimension values included in this object range from 0.1 to 0.8 (as is visible in the contours specification above.) Of course, nearly all HoloViews objects have dimensions, such as ``x`` and ``y`` in this case, but those dimensions are not used in the filenames because they are explicitly shown in the plots; only the top-level dimensions are used (those that determine which plot this is, not those that are shown in the plot itself.)\n",
"The ``{dimension}`` shows what dimension values are included anywhere in this object, if it contains any high-level ``Dimensioned`` objects like ``HoloMap``, ``NdOverlay``, and ``GridSpace``. In the last SVG image in the contents list above, which is for the ``contours`` object, there is one dimension ``Levels``, and the name shows that dimension values included in this object range from 0.1 to 0.8 (as is visible in the contours specification above.) Of course, nearly all HoloViews objects have dimensions, such as ``x`` and ``y`` in this case, but those dimensions are not used in the filenames because they are explicitly shown in the plots; only the top-level dimensions are used (those that determine which plot this is, not those that are shown in the plot itself.)\n",
"\n",
"The ``{group}-{label}`` information lists the names HoloViews uses for default titles and for attribute access for the various objects that make up a given displayed object. E.g. the first SVG image in the list is a ``Layout`` of the three given ``Image`` objects, and the second one is an ``Overlay`` of an ``RGB`` object and an ``Arrow`` object. This information usually helps distinguish one plot from another, because they will typically be plots of objects that have different labels. \n",
"\n",
Expand Down

0 comments on commit 6f505a1

Please sign in to comment.