Element
s 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 Element
s directly out of Python or Numpy data structures. An even more powerful way to use them is by collecting similar Element
s 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 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 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 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. For convenience, in this tutorial we have specified %output info=True
, which will pop up a detailed list and explanation of the available options for visualizing each Element
type, after that notebook cell is executed. So, 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:
Charts:
Chart3D Elements:
Rasters:
Tabular Elements:
Annotations:
Paths:
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 in {type}.{group}.{label}
format:
In [ ]:
import holoviews as hv
%reload_ext holoviews.ipython
hv.Element(None, group='Value', label='Label')
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.
Visualization of a dependent variable against an independent variable
The first large class of Elements
is the Chart
elements. These objects are by default indexable and sliceable along the x-axis, but not the y-axis, because they are intended for data values y measured for a given x value. However two key dimensions may be supplied to allow 2D indexing on these types. By default the data is expected to be laid out on a single key dimension x, with the data values ranging over a single value dimension y.
The data itself maybe supplied in one of three formats, however internally the data will always be held as a numpy array of shape (N, D), where N is the number of samples and D the number of dimensions. The accepted formats are:
1) As a numpy array of shape (N, D).
2) As a list of length N containing tuples of length D.
3) As a tuple of length D containing iterables of length N.
In [ ]:
import numpy as np
points = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
hv.Curve(points)
A Curve
is a set of values provided for some set of keys from a continuously indexable 1D coordinate system.
In [ ]:
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)
ErrorBars
is a set of x-/y-coordinates with associated error values, which 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 [ ]:
%%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'])
Spread
elements have the same data format as the ErrorBars
element, name x- and y-values with associated symmetric or assymetric errors.
In [ ]:
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))
In [ ]:
%%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'])
In [ ]:
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':'four']
Bars
is an NdElement
type so by default it is sorted. To inherit the initial ordering specify the Dimension
with values set to 'initial', alternatively 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 these 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. Additionally you may 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'.
In [ ]:
%%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'])
In [ ]:
np.random.seed(1)
data = [np.random.normal() for i in range(10000)]
frequencies, edges = np.histogram(data, 20)
hv.Histogram(frequencies, edges)
Almost all Element types may be projected onto a polar axis by supplying projection='polar'
as a plot option.
In [ ]:
%%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'])
In [ ]:
%%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]
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.
In [ ]:
np.random.seed(12)
points = np.random.rand(50,2)
hv.Points(points) + hv.Points(points)[0.6:0.8,0.2:0.5]
As you can see, Points
is very similar to Scatter
, but it is sliceable in both x and $y$, not just x, and so the right-hand plots are different for these two Element
s. Even though they can take the same input data, the Points
object treats both x and y as key_dimension
s, while Scatter
has a single key_dimension
x and a single value_dimension
y:
In [ ]:
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)))
Thus 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_dimension
s z and α visualized as the color and size of the dots, respectively:
In [ ]:
%%opts Points [color_index=2 size_index=3 scaling_factor=50]
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()
In [ ]:
x,y = 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)
As you can see above, the x and y positions are in 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 [ ]:
%%opts VectorField.A [color_dim='angle'] VectorField.M [color_dim='magnitude']
hv.VectorField(vector_data, group='A')
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 (below), the side histogram doubles as a color bar or key.
In [ ]:
import numpy as np
np.random.seed(42)
points = [(i, np.random.normal()) for i in range(800)]
hv.Scatter(points).hist()
In [ ]:
%%opts Surface (cmap='jet' rstride=20, cstride=2)
hv.Surface(np.sin(np.linspace(0,100*np.pi*2,10000)).reshape(100,100))
In [ ]:
%%opts Scatter3D [azimuth=40 elevation=20]
x,y = np.mgrid[-5:5, -5:5] * 0.1
heights = np.sin(x**2+y**2)
hv.Scatter3D(zip(x.flat,y.flat,heights.flat))
The Trisurface
Element renders any collection of 3D points as a Surface by applying Delaunay triangulation.
In [ ]:
%%opts Trisurface [fig_size=200] (cmap='hot_r')
hv.Trisurface((x.flat,y.flat,heights.flat))
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 two-dimensional space. For the Image
, RGB
, and HSV
elements, the coordinates of this two-dimensional space are defined in a continuously indexable coordinate system.
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 [ ]:
x,y = np.mgrid[-50:51, -50:51] * 0.1
hv.Raster(np.sin(x**2+y**2))
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 [ ]:
n = 21
xs = np.logspace(1, 3, n)
ys = np.linspace(1, 10, n)
X,Y = np.meshgrid(xs[:-1], ys[:-1]);
Z = np.sqrt(X**2 + Y**2)
hv.QuadMesh((xs, ys, np.random.rand(n-1, n-1)))
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 [ ]:
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))
A HeatMap
displays like a typical raster image, but the input is a dictionary indexed with two-dimensional keys, not a Numpy array. 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 , 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 [ ]:
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)
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.
In [ ]:
x,y = np.mgrid[-50:51, -50:51] * 0.1
bounds=(-1,-1,1,1) # Coordinate system: (left, bottom, top, right)
(hv.Image(np.sin(x**2+y**2), bounds=bounds)
+ hv.Image(np.sin(x**2+y**2), bounds=bounds)[-0.5:0.5, -0.5:0.5])
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 the from holoviews import *
statement that we use in these tutorials for brevity. 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()
.
The RGB
element is an Image
that supports red, green, blue channels:
In [ ]:
x,y = np.mgrid[-50:51, -50:51] * 0.1
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]))
You can see how the RGB object is created from the original channels:
In [ ]:
%%opts Image (cmap='gray')
hv.Image(r,label="R") + hv.Image(g,label="G") + hv.Image(b,label="B")
RGB
also supports an optional alpha channel, which will be used as a mask revealing or hiding any Element
s it is overlaid on top of:
In [ ]:
%%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
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 [ ]:
x,y = np.mgrid[-50:51, -50:51] * 0.1
h = 0.5 + np.sin(0.2*(x**2+y**2)) / 2.0
s = 0.5*np.cos(y*3)+0.5
v = 0.5*np.cos(x*3)+0.5
hv.HSV(np.dstack([h, s, v]))
You can see how this is created from the original channels:
In [ ]:
%%opts Image (cmap='gray')
hv.Image(h, label="H") + hv.Image(s, label="S") + hv.Image(v, label="V")
General data structures for holding arbitrary information
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 [ ]:
hv.ItemTable([('Age', 10), ('Weight',15), ('Height','0.8 meters')])
A table is more general than an ItemTable
, as it allows multi-dimensional keys and multidimensional values.
In [ ]:
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
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 [ ]:
table.select(Gender='M') + table.select(Gender='M', Age=10)
The Table
is used as a common data structure that may be converted to any other HoloViews data structure using the TableConversion
class. A similar principle holds when converting data from Pandas DataFrames to HoloViews objects using the optional Pandas support.
The functionality of the TableConversion
class may be conveniently accessed using the .to
property, which should have its own tutorial someday, but hopefully this will get the idea across:
In [ ]:
table.select(Gender='M').to.curve(kdims=["Age"], vdims=["Weight"])
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 [ ]:
scene = hv.RGB.load_image('../assets/penguins.png')
In [ ]:
scene * hv.VLine(-0.05) + scene * hv.HLine(-0.05)
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 [ ]:
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)
In [ ]:
scene * hv.Text(0, 0.2, 'Adult\npenguins') + scene * hv.Arrow(0,-0.1, 'Baby penguin', 'v')
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 however
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.
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 [ ]:
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])
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 [ ]:
x,y = np.mgrid[-50:51, -50:51] * 0.1
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)
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 as heatmaps with arbitrary shapes.
In [ ]:
%%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)
Polygons without a value are useful as annotation, but also allow us to draw arbitrary shapes.
In [ ]:
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)])(style={'facecolor': '#a50d0d'})
* hv.Polygons([rectangle(x=2, height=2), rectangle(x=5, height=2)])(style={'facecolor': '#ffcc00'})
* hv.Polygons([rectangle(x=3, height=2, width=2)])(style={'facecolor': 'c', 'hatch':'x'}))
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 [ ]:
scene * hv.Bounds(0.2) * hv.Bounds((0.45, 0.45, 0.2, 0.2))
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 round shape.
In [ ]:
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)