Elements

Elements are the basic building blocks for any HoloViews visualization. These are the objects that can be composed together using the various Container types. Here in this overview, we show an example of how to build each of these Elements directly out of Python or Numpy data structures. An even more powerful way to use them is by collecting similar Elements into a HoloMap, as described in Exploring Data, so that you can explore, select, slice, and animate them flexibly, but here we focus on having small, self-contained examples. Complete reference material for each type can be accessed using our documentation system. This tutorial uses the default matplotlib plotting backend; see the Bokeh Elements tutorial for the corresponding bokeh plots.

Element types

This class hierarchy shows each of the Element types. Each type is named for the default or expected way that the underlying data can be visualized. E.g., if your data is wrapped into a Surface object, it will display as a 3D surface by default, whereas the same data embedded in an Image object will display as a 2D raster image. But please note that the specification and implementation for each Element type does not actually include any such visualization -- the name merely serves as a semantic indication that you ordinarily think of the data as being laid out visually in that way. The actual plotting is done by a separate plotting subsystem, while the objects themselves focus on storing your data and the metadata needed to describe and use it.

This separation of data and visualization is described in detail in the Options tutorial, which describes all about how to find out the options available for each Element type and change them if necessary, from either Python or IPython Notebook. When using this tutorial interactively in an IPython/Jupyter notebook session, we suggest adding %output info=True after the call to notebook_extension below, which will pop up a detailed list and explanation of the available options for visualizing each Element type, after that notebook cell is executed. Then, to find out all the options for any of these Element types, just press <Shift-Enter> on the corresponding cell in the live notebook.

The types available:

Element
The base class of all Elements.

Charts:

Curve
A continuous relation between a dependent and an independent variable.
ErrorBars
A collection of x-/y-coordinates with associated error magnitudes.
Spread
Continuous version of ErrorBars.
Area
Bars
Data collected and binned into categories.
Histogram
Data collected and binned in a continuous space using specified bin edges.
BoxWhisker
Distributions of data varying by 0-N key dimensions.
Scatter
Discontinuous collection of points indexed over a single dimension.
Points
Discontinuous collection of points indexed over two dimensions.
VectorField
Cyclic variable (and optional auxiliary data) distributed over two-dimensional space.
Spikes
A collection of horizontal or vertical lines at various locations with fixed height (1D) or variable height (2D).
SideHistogram
Histogram binning data contained by some other Element.

Chart3D Elements:

Surface
Continuous collection of points in a three-dimensional space.
Scatter3D
Discontinuous collection of points in a three-dimensional space.
TriSurface
Continuous but irregular collection of points interpolated into a Surface using Delaunay triangulation.

Raster Elements:

Raster
The base class of all rasters containing two-dimensional arrays.
QuadMesh
Raster type specifying 2D bins with two-dimensional array of values.
HeatMap
Raster displaying sparse, discontinuous data collected in a two-dimensional space.
Image
Raster containing a two-dimensional array covering a continuous space (sliceable).
RGB
Image with 3 (R,G,B) or 4 (R,G,B,Alpha) color channels.
HSV
Image with 3 (Hue, Saturation, Value) or 4 channels.

Tabular Elements:

ItemTable
Ordered collection of key-value pairs (ordered dictionary).
Table
Collection of arbitrary data with arbitrary key and value dimensions.

Annotations:

VLine
Vertical line annotation.
HLine
Horizontal line annotation.
Spline
Bezier spline (arbitrary curves).
Text
Text annotation on an Element.
Arrow
Arrow on an Element with optional text label.

Paths:

Path
Collection of paths.
Contours
Collection of paths, each with an associated value.
Polygons
Collection of filled, closed paths with an associated value.
Bounds
Box specified by corner positions.
Box
Box specified by center position, radius, and aspect ratio.
Ellipse
Ellipse specified by center position, radius, and aspect ratio.

Element

The basic or fundamental types of data that can be visualized.

Element is the base class for all the other HoloViews objects shown in this section.

All Element objects accept data as the first argument to define the contents of that element. In addition to its implicit type, each element object has a group string defining its category, and a label naming this particular item, as described in the Introduction.

When rich display is off, or if no visualization has been defined for that type of Element, the Element is presented with a default textual representation:

In [1]:
import holoviews as hv
hv.notebook_extension()
hv.Element(None, group='Value', label='Label')
Out[1]:
:Element

In addition, Element has key dimensions (kdims), value dimensions (vdims), and constant dimensions (cdims) to describe the semantics of indexing within the Element, the semantics of the underlying data contained by the Element, and any constant parameters associated with the object, respectively. Dimensions are described in the Introduction.

The remaining Element types each have a rich, graphical display as shown below.

Chart Elements

Visualization of a dependent variable against an independent variable

The first large class of Elements is the Chart elements. These objects have at least one fully indexable, sliceable key dimension (typically the x axis in a plot), and usually have one or more value dimension(s) (often the y axis) that may or may not be indexable depending on the implementation. The key dimensions are normally the parameter settings for which things are measured, and the value dimensions are the data points recorded at those settings.

As described in the Columnar Data tutorial, the data can be stored in several different internal formats, such as a NumPy array of shape (N, D), where N is the number of samples and D the number of dimensions. A somewhat larger list of formats can be accepted, including any of the supported internal formats, or

  1. As a list of length N containing tuples of length D.
  2. As a tuple of length D containing iterables of length N.

Curve

In [2]:
import numpy as np
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
hv.Curve(points)
Out[2]:

A Curve is a set of values provided for some set of keys from a continuously indexable 1D coordinate system, where the plotted values will be connected up because they are assumed to be samples from a continuous relation.

ErrorBars

In [3]:
np.random.seed(7)
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
errors = [(0.1*i, np.sin(0.1*i), np.random.rand()/2) for i in np.linspace(0, 100, 11)]
hv.Curve(points) * hv.ErrorBars(errors)
Out[3]:

ErrorBars is a set of x-/y-coordinates with associated error values. Error values may be either symmetric or asymmetric, and thus can be supplied as an Nx3 or Nx4 array (or any of the alternative constructors Chart Elements allow).

In [4]:
%%opts ErrorBars (capthick=3)
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
errors = [(0.1*i, np.sin(0.1*i), np.random.rand()/2, np.random.rand()/4) for i in np.linspace(0, 100, 11)]
hv.Curve(points) * hv.ErrorBars(errors, vdims=['y', 'yerrneg', 'yerrpos'])
Out[4]:

Spread

Spread elements have the same data format as the ErrorBars element, namely x- and y-values with associated symmetric or asymmetric errors, but are interpreted as samples from a continuous distribution (just as Curve is the continuous version of Scatter). These are often paired with an overlaid Curve to show both the mean (as a curve) and the spread of values; see the Columnar Data tutorial for examples.

Symmetric
In [5]:
np.random.seed(42)
xs = np.linspace(0, np.pi*2, 20)
err = 0.2+np.random.rand(len(xs))
hv.Spread((xs, np.sin(xs), err))
Out[5]:
Asymmetric
In [6]:
%%opts Spread (facecolor='indianred' alpha=1)
xs = np.linspace(0, np.pi*2, 20)
hv.Spread((xs, np.sin(xs), 0.1+np.random.rand(len(xs)), 0.1+np.random.rand(len(xs))),
          vdims=['y', 'yerrneg', 'yerrpos'])
Out[6]:

Area

Area under the curve

By default the Area Element draws just the area under the curve, i.e. the region between the curve and the origin.

In [7]:
xs = np.linspace(0, np.pi*4, 40)
hv.Area((xs, np.sin(xs)))
Out[7]:

Area between curves

When supplied a second value dimension the area is defined as the area between two curves.

In [8]:
X  = np.linspace(0,3,200)
Y = X**2 + 3
Y2 = np.exp(X) + 2
Y3 = np.cos(X)
hv.Area((X, Y, Y2), vdims=['y', 'y2']) * hv.Area((X, Y, Y3), vdims=['y', 'y3'])
Out[8]:

Stacked areas

Areas are also useful to visualize multiple variables changing over time, but in order to be able to compare them the areas need to be stacked. Therefore the operation module provides the stack_area operation which makes it trivial to stack multiple Area in an (Nd)Overlay.

In this example we will generate a set of 5 arrays representing percentages and create an Overlay of them. Then we simply call the stack_area operation on the Overlay to get a stacked area chart.

In [9]:
values = np.random.rand(5, 20)
percentages = (values/values.sum(axis=0)).T*100

overlay = hv.Overlay([hv.Area(percentages[:, i], vdims=[hv.Dimension('value', unit='%')]) for i in range(5)])
overlay + hv.Area.stack(overlay)
Out[9]:

Bars

In [10]:
data = [('one',8),('two', 10), ('three', 16), ('four', 8), ('five', 4), ('six', 1)]
bars = hv.Bars(data, kdims=[hv.Dimension('Car occupants', values='initial')], vdims=['Count'])
bars + bars[['one', 'two', 'three']]
WARNING:root:Dimension: The 'initial' string for dimension values is no longer supported.
Out[10]:

Bars is an NdElement type, so by default it is sorted. To preserve the initial ordering specify the Dimension with values set to 'initial', or you can supply an explicit list of valid dimension keys.

Bars support up to three key dimensions which can be laid by 'group', 'category', and 'stack' dimensions. By default the key dimensions are mapped onto the first, second, and third Dimension of the Bars object, but this behavior can be overridden via the group_index, category_index, and stack_index options. You can also style each bar the way you want by creating style groups for any combination of the three dimensions. Here we color_by 'category' and 'stack', so that a given color represents some combination of those two values (according to the key shown).

In [11]:
%%opts Bars [color_by=['category', 'stack'] legend_position='top']
from itertools import product
np.random.seed(1)
groups, categories, stacks = ['A', 'B'], ['a', 'b'], ['I', 'II']
keys = product(groups, categories, stacks)
hv.Bars([(k, np.random.rand()*100) for k in keys],
         kdims=['Group', 'Category', 'Stack'], vdims=['Count'])
Out[11]:

BoxWhisker

The BoxWhisker Element allows representing distributions of data varying by 0-N key dimensions. To represent the distribution of a single variable, we can create a BoxWhisker Element with no key dimensions and a single value dimension:

In [12]:
hv.BoxWhisker(np.random.randn(200), kdims=[], vdims=['Value'])
Out[12]:

BoxWhisker Elements support any number of dimensions and may also be rotated. To style the boxes and whiskers, supply boxprops, whiskerprops, and flierprops.

In [13]:
%%opts BoxWhisker [fig_size=200 invert_axes=True]
style = dict(boxprops=dict(color='gray', linewidth=1), whiskerprops=dict(color='indianred', linewidth=1))
groups = [chr(65+g) for g in np.random.randint(0, 3, 200)]
hv.BoxWhisker((groups, np.random.randint(0, 5, 200), np.random.randn(200)),
              kdims=['Group', 'Category'], vdims=['Value']).opts(style=style).sort()
Out[13]:

BoxWhisker Elements may also be used to represent a distribution as a marginal plot by adjoining it using <<.

In [14]:
points = hv.Points(np.random.randn(500, 2))
points << hv.BoxWhisker(points['y']) << hv.BoxWhisker(points['x'])
Out[14]:

Histogram

In [15]:
np.random.seed(1)
data = [np.random.normal() for i in range(10000)]
frequencies, edges = np.histogram(data, 20)
hv.Histogram(frequencies, edges)
WARNING:root:Histogram: Histogram edges should be supplied as a tuple along with the values, passing the edges will be deprecated in holoviews 2.0.
Out[15]:

Histograms partition the x axis into discrete (but not necessarily regular) bins, showing counts in each as a bar.

Almost all Element types, including Histogram, may be projected onto a polar axis by supplying projection='polar' as a plot option.

In [16]:
%%opts Histogram [projection='polar' show_grid=True]
data = [np.random.rand()*np.pi*2 for i in range(100)]
frequencies, edges = np.histogram(data, 20)
hv.Histogram(frequencies, edges, kdims=['Angle'])
WARNING:root:Histogram: Histogram edges should be supplied as a tuple along with the values, passing the edges will be deprecated in holoviews 2.0.
Out[16]:

Scatter

In [17]:
%%opts Scatter (color='k', marker='s', s=50)
np.random.seed(42)
points = [(i, np.random.random()) for i in range(20)]
hv.Scatter(points) + hv.Scatter(points)[12:20]
Out[17]:

Scatter is the discrete equivalent of Curve, showing y values for discrete x values selected. See Points for more information.

The marker shape specified above can be any supported by matplotlib, e.g. s, d, or o; the other options select the color and size of the marker. For convenience with the bokeh backend, the matplotlib marker options are supported using a compatibility function in HoloViews.

Points

In [18]:
np.random.seed(12)
points = np.random.rand(50,2)
hv.Points(points) + hv.Points(points)[0.6:0.8,0.2:0.5]
Out[18]:

As you can see, Points is very similar to Scatter, and can produce some plots that look identical. However, the two Elements are very different semantically. For Scatter, the dots each show a dependent variable y for some x, such as in the Scatter example above where we selected regularly spaced values of x and then created a random number as the corresponding y. I.e., for Scatter, the y values are the data; the xs are just where the data values are located. For Points, both x and y are independent variables, known as key_dimensions in HoloViews:

In [19]:
for o in [hv.Points(points,name="Points "), hv.Scatter(points,name="Scatter")]:
    for d in ['key','value']:
        print("%s %s_dimensions: %s " % (o.name, d, o.dimensions(d,label=True)))
Points  key_dimensions: ['x', 'y'] 
Points  value_dimensions: [] 
Scatter key_dimensions: ['x'] 
Scatter value_dimensions: ['y'] 

The Scatter object expresses a dependent relationship between x and y, making it useful for combining with other similar Chart types, while the Points object expresses the relationship of two independent keys x and y with optional vdims (zero in this case), which makes Points objects meaningful to combine with the Raster types below.

Of course, the vdims need not be empty for Points; here is an example with two additional quantities for each point, as value_dimensions z and α visualized as the color and size of the dots, respectively. The point sizes can be tweaked using the option scaling_factor, which determines the amount by which each point width or area is scaled, depending on the value of scaling_method.

In [20]:
%%opts Points [color_index=2 size_index=3 scaling_method="width" scaling_factor=10]
np.random.seed(10)
data = np.random.rand(100,4)

points = hv.Points(data, vdims=['z', 'alpha'])
points + points[0.3:0.7, 0.3:0.7].hist()
Out[20]:

Such a plot wouldn't be meaningful for Scatter, but is a valid use for Points, where the x and y locations are independent variables representing coordinates, and the "data" is conveyed by the size and color of the dots.

Spikes

Spikes represent any number of horizontal or vertical line segments with fixed or variable heights. There are a number of disparate uses for this type. First of all, they may be used as a rugplot to give an overview of a one-dimensional distribution. They may also be useful in more domain-specific cases, such as visualizing spike trains for neurophysiology or spectrograms in physics and chemistry applications.

In the simplest case, a Spikes object represents coordinates in a 1D distribution:

In [21]:
%%opts Spikes (alpha=0.4)
xs = np.random.rand(50)
ys = np.random.rand(50)
hv.Points((xs, ys)) * hv.Spikes(xs)
Out[21]:

When supplying two dimensions to the Spikes object, the second dimension will be mapped onto the line height. Optionally, you may also supply a cmap and color_index to map color onto one of the dimensions. This way we can, for example, plot a mass spectrogram:

In [22]:
%%opts Spikes (cmap='Reds')
hv.Spikes(np.random.rand(20, 2), kdims=['Mass'], vdims=['Intensity'])
Out[22]:

Another possibility is to draw a number of spike trains as you would encounter in neuroscience. Here we generate 10 separate random spike trains and distribute them evenly across the space by setting their position. By also declaring some yticks, each spike train can be labeled individually:

In [23]:
%%opts Spikes NdOverlay [show_legend=False]
hv.NdOverlay({i: hv.Spikes(np.random.randint(0, 100, 10), kdims=['Time']).opts(plot=dict(position=0.1*i))
              for i in range(10)}).opts(plot=dict(yticks=[((i+1)*0.1-0.05, i) for i in range(10)]))
Out[23]:

Finally, we may use Spikes to visualize marginal distributions as adjoined plots using the << adjoin operator:

In [24]:
%%opts Spikes (alpha=0.05) [spike_length=1]
points = hv.Points(np.random.randn(500, 2))
points << hv.Spikes(points['y']) << hv.Spikes(points['x'])
Out[24]:

VectorField

In [25]:
%%opts VectorField [size_index=3]
y,x  = np.mgrid[-10:10,-10:10] * 0.25
sine_rings  = np.sin(x**2+y**2)*np.pi+np.pi
exp_falloff = 1/np.exp((x**2+y**2)/8)

vector_data = [x,y,sine_rings, exp_falloff]
hv.VectorField(vector_data)
Out[25]:

As you can see above, here the x and y positions are chosen to make a regular grid. The arrow angles follow a sinsoidal ring pattern, and the arrow lengths fall off exponentially from the center, so this plot has four dimensions of data (direction and length for each x,y position).

Using the IPython %%opts cell-magic (described in the Options tutorial, along with the Python equivalent), we can also use color as a redundant indicator to the direction or magnitude:

In [26]:
%%opts VectorField [size_index=3] VectorField.A [color_index=2] VectorField.M [color_index=3]
hv.VectorField(vector_data, group='A') + hv.VectorField(vector_data, group='M')
Out[26]:

The vector fields above were sampled on a regular grid, but any collection of x,y values is allowed:

In [27]:
n=20
x=np.linspace(1,3,n)
y=np.sin(np.linspace(0,2*np.pi,n))/4
hv.VectorField([x,y,x*5,np.ones(n)]) * hv.VectorField([x,-y,x*5,np.ones(n)])
Out[27]:

SideHistogram

The .hist method conveniently adjoins a histogram to the side of any Chart, Surface, or Raster component, as well as many of the container types (though it would be reporting data from one of these underlying Element types). For a Raster using color or grayscale to show values (see Raster section below), the side histogram doubles as a color bar or key.

In [28]:
import numpy as np
np.random.seed(42)
points = [(i, np.random.normal()) for i in range(800)]
hv.Scatter(points).hist()
Out[28]:

Chart3D Elements

Surface

In [29]:
%%opts Surface (cmap='jet' rstride=20, cstride=2)
hv.Surface(np.sin(np.linspace(0,100*np.pi*2,10000)).reshape(100,100))
Out[29]:

Surface is used for a set of gridded points whose associated value dimension represents samples from a continuous surface; it is the equivalent of a Curve but with two key dimensions instead of just one.

Scatter3D

In [30]:
%%opts Scatter3D [azimuth=40 elevation=20]
y,x = np.mgrid[-5:5, -5:5] * 0.1
heights = np.sin(x**2+y**2)
hv.Scatter3D(zip(x.flat,y.flat,heights.flat))
Out[30]:

Scatter3D is the equivalent of Scatter but for two key dimensions, rather than just one.

TriSurface

The TriSurface Element renders any collection of 3D points as a Surface by applying Delaunay triangulation. It thus supports arbitrary, non-gridded data, but it does not support indexing to find data values, since finding the closest ones would require a search.

In [31]:
%%opts TriSurface [fig_size=200] (cmap='hot_r')
hv.TriSurface((x.flat,y.flat,heights.flat))
Out[31]:

Raster Elements

A collection of raster image types

The second large class of Elements is the raster elements. Like Points and unlike the other Chart elements, Raster Elements live in a 2D key-dimensions space. For the Image, RGB, and HSV elements, the coordinates of this two-dimensional key space are defined in a continuously indexable coordinate system. We can use np.meshgrid to define the appropriate sampling along the x and y dimensions:

In [32]:
x,y = np.meshgrid(np.linspace(-5,5,101), np.linspace(5,-5,101))

Raster

A Raster is the base class for image-like Elements, but may be used directly to visualize 2D arrays using a color map. The coordinate system of a Raster is the raw indexes of the underlying array, with integer values always starting from (0,0) in the top left, with default extents corresponding to the shape of the array. The Image subclass visualizes similarly, but using a continuous Cartesian coordinate system suitable for an array that represents some underlying continuous region.

In [33]:
hv.Raster(np.sin(x**2+y**2))
Out[33]:

QuadMesh

The basic QuadMesh is a 2D grid of bins specified as x-/y-values specifying a regular sampling or edges, with arbitrary sampling and an associated 2D array containing the bin values. The coordinate system of a QuadMesh is defined by the bin edges, therefore any index falling into a binned region will return the appropriate value. Unlike Image objects, slices must be inclusive of the bin edges.

In [34]:
n = 21
xs = np.logspace(1, 3, n)
ys = np.linspace(1, 10, n)
hv.QuadMesh((xs, ys, np.random.rand(n-1, n-1)))
Out[34]:

QuadMesh may also be used to represent an arbitrary mesh of quadrilaterals by supplying three separate 2D arrays representing the coordinates of each quadrilateral in a 2D space. Note that when using QuadMesh in this mode, slicing and indexing semantics and most operations will currently not work.

In [35]:
coords = np.linspace(-1.5,1.5,n)
X,Y = np.meshgrid(coords, coords);
Qx = np.cos(Y) - np.cos(X)
Qz = np.sin(Y) + np.sin(X)
Z = np.sqrt(X**2 + Y**2)
hv.QuadMesh((Qx, Qz, Z))
Out[35]:

HeatMap

A HeatMap displays like a typical raster image, but the input is a dictionary indexed with two-dimensional keys, not a Numpy array or Pandas dataframe. As many rows and columns as required will be created to display the values in an appropriate grid format. Values unspecified are left blank, and the keys can be any Python datatype (not necessarily numeric). One typical usage is to show values from a set of experiments, such as a parameter space exploration, and many other such visualizations are shown in the Containers and Exploring Data tutorials. Each value in a HeatMap is labeled explicitly by default, and so this component is not meant for very large numbers of samples. With the default color map, high values (in the upper half of the range present) are colored orange and red, while low values (in the lower half of the range present) are colored shades of blue.

In [36]:
data = {(chr(65+i),chr(97+j)): i*j for i in range(5) for j in range(5) if i!=j}
hv.HeatMap(data).sort()
Out[36]:

Image

Like Raster, a HoloViews Image allows you to view 2D arrays using an arbitrary color map. Unlike Raster, an Image is associated with a 2D coordinate system in continuous space, which is appropriate for values sampled from some underlying continuous distribution (as in a photograph or other measurements from locations in real space). Slicing, sampling, etc. on an Image all use this continuous space, whereas the corresponding operations on a Raster work on the raw array coordinates.

To make the coordinate system clear, we'll define two arrays called xs and ys with a non-square aspect and map them through a simple function that illustrate how these inputs relate to the coordinate system:

In [37]:
bounds=(-2,-3,5,2)   # Coordinate system: (left, bottom, top, right)
xs,ys = np.meshgrid(np.linspace(-2,5,50), np.linspace(2,-3, 30))
(hv.Image(np.sin(xs)+ys, bounds=bounds) 
 + hv.Image(np.sin(xs)+ys, bounds=bounds)[0:3, -2.5:2])
Out[37]:

Notice how, because our declared coordinate system is continuous, we can slice with any floating-point value we choose. The appropriate range of the samples in the input numpy array will always be displayed, whether or not there are samples at those specific floating-point values.

It is also worth noting that the name Image can clash with other common libraries, which is one reason to avoid unqualified imports like from holoviews import *. For instance, the Python Imaging Libray provides an Image module, and IPython itself supplies an Image class in IPython.display. Python namespaces allow you to avoid such problems, e.g. using from PIL import Image as PILImage or using import holoviews as hv and then hv.Image(), as we do in these tutorials.

RGB

The RGB element is an Image that supports red, green, blue channels:

In [38]:
r = 0.5*np.sin(np.pi  +3*x**2+y**2)+0.5
g = 0.5*np.sin(x**2+2*y**2)+0.5
b = 0.5*np.sin(np.pi/2+x**2+y**2)+0.5

hv.RGB(np.dstack([r,g,b]))
Out[38]:

You can see how the RGB object is created from the original channels:

In [39]:
%%opts Image (cmap='gray')
hv.Image(r,label="R") + hv.Image(g,label="G") + hv.Image(b,label="B")
Out[39]:

RGB also supports an optional alpha channel, which will be used as a mask revealing or hiding any Elements it is overlaid on top of:

In [40]:
%%opts Image (cmap='gray')
mask = 0.5*np.sin(0.2*(x**2+y**2))+0.5
rgba = hv.RGB(np.dstack([r,g,b,mask]))

bg = hv.Image(0.5*np.cos(x*3)+0.5, label="Background") * hv.VLine(x=0,label="Background")

overlay = bg*rgba
overlay.label="RGBA Overlay"

bg + hv.Image(mask,label="Mask") + overlay
Out[40]:

HSV

HoloViews makes it trivial to work in any color space that can be converted to RGB by making a simple subclass of RGB as appropriate. For instance, we also provide the HSV (hue, saturation, value) color space, which is useful for plotting cyclic data (as the Hue) along with two additional dimensions (controlling the saturation and value of the color, respectively):

In [41]:
h = 0.5 + np.sin(0.2*(x**2+y**2)) / 2.0
s = 0.5*np.cos(x*3)+0.5
v = 0.5*np.cos(y*3)+0.5

hv.HSV(np.dstack([h, s, v]))
Out[41]:

You can see how this is created from the original channels:

In [42]:
%%opts Image (cmap='gray')
hv.Image(h, label="H") + hv.Image(s, label="S") + hv.Image(v, label="V")
Out[42]:

Tabular Elements

General data structures for holding arbitrary information

ItemTable

An ItemTable is an ordered collection of key, value pairs. It can be used to directly visualize items in a tabular format where the items may be supplied as an OrderedDict or a list of (key,value) pairs. A standard Python dictionary can be easily visualized using a call to the .items() method, though the entries in such a dictionary are not kept in any particular order, and so you may wish to sort them before display. One typical usage for an ItemTable is to list parameter values or measurements associated with an adjacent Element.

In [43]:
hv.ItemTable([('Age', 10), ('Weight',15), ('Height','0.8 meters')])
Out[43]:

Table

A table is more general than an ItemTable, as it allows multi-dimensional keys and multidimensional values.

In [44]:
keys =   [('M',10), ('M',16), ('F',12)]
values = [(15, 0.8), (18, 0.6), (10, 0.8)]
table = hv.Table(zip(keys,values), 
                 kdims = ['Gender', 'Age'], 
                 vdims=['Weight', 'Height'])
table
Out[44]:

Note that you can use select using tables, and once you select using a full, multidimensional key, you get an ItemTable (shown on the right):

In [45]:
table.select(Gender='M') + table.select(Gender='M', Age=10)
Out[45]:

The Table is used as a common data structure that may be converted to any other HoloViews data structure using the TableConversion class.

The functionality of the TableConversion class may be conveniently accessed using the .to property. For more extended usage of table conversion see the Columnar Data and Pandas Conversion Tutorials.

In [46]:
table.select(Gender='M').to.curve(kdims=["Age"], vdims=["Weight"])
Out[46]:

Annotation Elements

Useful information that can be overlaid onto other components

Annotations are components designed to be overlaid on top of other Element objects. To demonstrate annotation and paths, we will be drawing many of our elements on top of an RGB Image:

In [47]:
scene = hv.RGB.load_image('../assets/penguins.png')

VLine and HLine

In [48]:
scene * hv.VLine(-0.05) + scene * hv.HLine(-0.05)
Out[48]:

Spline

The Spline annotation is used to draw Bezier splines using the same semantics as matplotlib splines. In the overlay below, the spline is in dark blue and the control points are in light blue.

In [49]:
points = [(-0.3, -0.3), (0,0), (0.25, -0.25), (0.3, 0.3)]
codes = [1,4,4,4]
scene * hv.Spline((points,codes)) * hv.Curve(points)
Out[49]:

Text and Arrow

In [50]:
scene * hv.Text(0, 0.2, 'Adult\npenguins') + scene * hv.Arrow(0,-0.1, 'Baby penguin', 'v')
Out[50]:

Paths

Line-based components that can be overlaid onto other components

Paths are a subclass of annotations that involve drawing line-based components on top of other elements. Internally, Path Element types hold a list of Nx2 arrays, specifying the x/y-coordinates along each path. The data may be supplied in a number of ways, including:

  1. A list of Nx2 numpy arrays.
  2. A list of lists containing x/y coordinate tuples.
  3. A tuple containing an array of length N with the x-values and a second array of shape NxP, where P is the number of paths.
  4. A list of tuples each containing separate x and y values.

Path

A Path object is actually a collection of paths which can be arbitrarily specified. Although there may be multiple unconnected paths in a single Path object, they will all share the same style. Only by overlaying multiple Path objects do you iterate through the defined color cycle (or any other style options that have been defined).

In [51]:
angle = np.linspace(0, 2*np.pi, 100)
baby = list(zip(0.15*np.sin(angle),  0.2*np.cos(angle)-0.2))

adultR = [(0.25, 0.45), (0.35,0.35), (0.25, 0.25), (0.15, 0.35), (0.25, 0.45)]
adultL = [(-0.3, 0.4), (-0.3, 0.3), (-0.2, 0.3), (-0.2, 0.4),(-0.3, 0.4)]

scene * hv.Path([adultL, adultR, baby]) * hv.Path([baby])
Out[51]:

Contours

A Contours object is similar to Path object except each of the path elements is associated with a numeric value, called the level. Sadly, our penguins are too complicated to give a simple example so instead we will simply mark the first couple of rings of our earlier ring pattern:

In [52]:
def circle(radius, x=0, y=0):
    angles = np.linspace(0, 2*np.pi, 100)
    return np.array( list(zip(x+radius*np.sin(angles), y+radius*np.cos(angles))))

hv.Image(np.sin(x**2+y**2)) * hv.Contours([circle(0.22)], level=0) * hv.Contours([circle(0.33)], level=1)
WARNING:root:Contours: The level parameter on Contours elements is deprecated, supply the value dimension(s) as columns in the data.
WARNING:root:Contours: The level parameter on Contours elements is deprecated, supply the value dimension(s) as columns in the data.
Out[52]:

Polygons

A Polygons object is similar to a Contours object except that each supplied path is closed and filled. Just like Contours, optionally a level may be supplied; the Polygons will then be colored according to the supplied cmap. Non-finite values such as np.NaN or np.inf will default to the supplied facecolor.

Polygons with values can be used to build heatmaps with arbitrary shapes.

In [53]:
%%opts Polygons (cmap='hot' edgecolor='k' linewidth=2)
np.random.seed(35)
hv.Polygons([np.random.rand(4,2)], level=0.5) *\
hv.Polygons([np.random.rand(4,2)], level=1.0) *\
hv.Polygons([np.random.rand(4,2)], level=1.5) *\
hv.Polygons([np.random.rand(4,2)], level=2.0)
WARNING:root:Polygons: The level parameter on Polygons elements is deprecated, supply the value dimension(s) as columns in the data.
WARNING:root:Polygons: The level parameter on Polygons elements is deprecated, supply the value dimension(s) as columns in the data.
WARNING:root:Polygons: The level parameter on Polygons elements is deprecated, supply the value dimension(s) as columns in the data.
WARNING:root:Polygons: The level parameter on Polygons elements is deprecated, supply the value dimension(s) as columns in the data.
Out[53]:

Polygons without a value are useful as annotation, but also allow us to draw arbitrary shapes.

In [54]:
def rectangle(x=0, y=0, width=1, height=1):
    return np.array([(x,y), (x+width, y), (x+width, y+height), (x, y+height)])

(hv.Polygons([rectangle(width=2), rectangle(x=6, width=2)]).opts(style={'facecolor': '#a50d0d'})
* hv.Polygons([rectangle(x=2, height=2), rectangle(x=5, height=2)]).opts(style={'facecolor': '#ffcc00'})
* hv.Polygons([rectangle(x=3, height=2, width=2)]).opts(style={'facecolor': 'c', 'hatch':'x'}))
Out[54]:

Bounds

A bounds is a rectangular area specified as a tuple in (left, bottom, right, top) format. It is useful for denoting a region of interest defined by some bounds, whereas Box (below) is useful for drawing a box at a specific location.

In [55]:
scene * hv.Bounds(0.2) * hv.Bounds((0.45, 0.45, 0.2, 0.2))
Out[55]:

A Box is similar to a Bounds except you specify the box position, width, and aspect ratio instead of the coordinates of the box corners. An Ellipse is specified just as for Box, but has a rounded shape.

In [56]:
scene * hv.Box(    -0.25, 0.3, 0.3, aspect=0.5) * hv.Box(    0, -0.2, 0.1) + \
scene * hv.Ellipse(-0.25, 0.3, 0.3, aspect=0.5) * hv.Ellipse(0, -0.2, 0.1)
Out[56]:

Download this notebook from GitHub (right-click to download).