Styling Mapping

In [1]:
import numpy as np
import holoviews as hv
from holoviews import dim, opts

hv.extension('bokeh')

One of the major benefits of HoloViews is the fact that Elements are simple, declarative wrappers around your data, with clearly defined semantics describing how the dimensions of the data map to the screen. Usually the key dimensions (kdims) and value dimensions map to coordinates of the plot axes and/or the colormapped intensity. However there are a huge number of ways to augment the visual representation of an element by mapping dimensions to visual attributes. In this section we will explore how we can declare such mappings including complex transforms specified by so called dim objects.

To illustrate this point let us create a set of three points with x/y-coordinates and alpha, color, marker and size values and then map each of those value dimensions to a visual attribute by name. Note that by default kdims is x,y. However, in this example we also show that the names of the dimensions can be changed and we use 'x values' and 'y values' to represent the data series names.

In [2]:
data = {
    'x values': [0, 1, 0.5],
    'y values': [1, 0, 0.5],
    'alpha': [0.5, 1, 0.3],
    'color': ['red', 'blue', 'green'],
    'marker': ['circle', 'triangle', 'diamond'],
    'size': [15, 25, 40]
}

opts.defaults(opts.Points(size=8, line_color='black'))

hv.Points(data, kdims=['x values','y values'] , vdims=['alpha', 'color', 'marker', 'size']).opts(
    alpha='alpha', color='color', marker='marker', size='size')
Out[2]:

This is the simplest approach to style mapping, dimensions can be mapped to visual attributes directly by name. However often columns in the data will not directly map to a visual property, e.g. we might want to normalize values before mapping them to the alpha, or apply a scaling factor to some values before mapping them to the point size; this is where dim transforms come in. Below are a few examples of using dim transforms to map a dimension in the data to the visual style in the plot:

In [3]:
points = hv.Points(np.random.rand(400, 4))

bins   = [0, .25, 0.5, .75, 1]
labels = ['circle', 'triangle', 'diamond', 'square']

layout = hv.Layout([
    points.relabel('Alpha' ).opts(alpha =dim('x').norm()),
    points.relabel('Angle' ).opts(angle =dim('x').norm()*360, marker='dash'),
    points.relabel('Color' ).opts(color =dim('x')),
    points.relabel('Marker').opts(marker=dim('x').bin(bins, labels)),
    points.relabel('Size'  ).opts(size  =dim('x')*10)
])

layout.opts(opts.Points(width=250, height=250, xaxis=None, yaxis=None)).cols(5)
Out[3]:

What are dim transforms?

In the above example we saw how to use an dim to define a transform from a dimension in your data to the visual property on screen. A dim therefore is a simple way to declare a deferred transform of your data. In the simplest case an dim simply returns the data for a dimension without transforming it, e.g. to look up the 'alpha' dimension on the points object we can create an dim and use the apply method to evaluate the expression:

In [4]:
from holoviews import dim

ds = hv.Dataset(np.random.rand(10, 4)*10, ['x', 'y'], ['alpha', 'size'])

dim('alpha').apply(ds)
Out[4]:
array([7.85909389, 4.84129285, 8.09987676, 3.25411015, 7.8969093 ,
       4.50949599, 8.19812605, 4.76671214, 4.95105192, 2.94366296])

Mathematical operators

An dim declaration allow arbitrary mathematical operations to be performed, e.g. let us declare that we want to subtract 5 from the 'alpha' dimension and then compute the min:

In [5]:
math_op = (dim('alpha')-5).min()
math_op
Out[5]:
(dim('alpha')-5).min()

Printing the repr of the math_op we can see that it builds up an nested expression. To see the transform in action we will once again apply it on the points:

In [6]:
math_op.apply(ds)
Out[6]:
-2.056337040475753

dim objects implement most of the NumPy API, supports all standard mathematical operators and also support NumPy ufuncs.

Custom functions

In addition to standard mathematical operators it is also possible to declare custom functions which can be applied by name. By default HoloViews ships with three commonly useful functions.

norm

Unity based normalization or features scaling normalizing the values to a range between 0-1 (optionally accepts min/max values as limits, which are usually provided by the plotting system) using the expression:

(values - min) / (max-min)

for example we can rescale the alpha values into a 0-1 range:

In [7]:
dim('alpha').norm().apply(ds)
Out[7]:
array([0.9354773 , 0.3611463 , 0.98130174, 0.05908257, 0.94267411,
       0.29800058, 1.        , 0.34695251, 0.38203503, 0.        ])
bin

Bins values using the supplied bins specified as the edges of each bin:

In [8]:
bin_op = dim('alpha').bin([0, 5, 10])

bin_op.apply(ds)
Out[8]:
array([7.5, 2.5, 7.5, 2.5, 7.5, 2.5, 7.5, 2.5, 2.5, 2.5])

It is also possible to provide explicit labels for each bin which will replace the bin center value:

In [9]:
dim('alpha').bin([0, 5, 10], ['Bin 1', 'Bin 2']).apply(ds)
Out[9]:
array(['Bin 2', 'Bin 1', 'Bin 2', 'Bin 1', 'Bin 2', 'Bin 1', 'Bin 2',
       'Bin 1', 'Bin 1', 'Bin 1'], dtype=object)
categorize

Maps a number of discrete values onto the supplied list of categories, e.g. having binned the data into 2 discrete bins we can map them to two discrete marker types 'circle' and 'triangle':

In [10]:
dim(bin_op).categorize({2.5: 'circle', 7.5: 'square'}).apply(ds)
Out[10]:
array(['square', 'circle', 'square', 'circle', 'square', 'circle',
       'square', 'circle', 'circle', 'circle'], dtype=object)

This can be very useful to map discrete categories to markers or colors.

Style mapping with dim transforms

This allows a huge amount of flexibility to express how the data should be mapped to visual style without directly modifying the data. To demonstrate this we will use some of the more complex:

In [11]:
points.opts(
    alpha =(dim('x')+0.2).norm(),
    angle =np.sin(dim('y'))*360,
    color =dim('x')**2,
    marker=dim('y').bin(bins, labels),
    size  =dim('x')**dim('y')*20, width=500, height=500)
Out[11]:

Let's summarize the style transforms we have applied:

  • alpha=(dim('x')+0.2).norm(): The alpha are mapped to the x-values offset by 0.2 and normalized.
  • angle=np.sin(dim('x'))*360: The angle of each marker is the sine of the y-values, multiplied by 360
  • color='x': The points are colormapped by square of their x-values.
  • marker=dim('y').bin(bins, labels): The y-values are binned and each bin is assignd a unique marker.
  • size=dim('x')**dim('y')*20: The size of the points is mapped to the x-values exponentiated with the y-values and scaled by 20

These are simply illustrative examples, transforms can be chained in arbitrarily complex ways to achieve almost any mapping from dimension values to visual style.

Colormapping

Color cycles and styles are useful for categorical plots and when overlaying multiple subsets, but when we want to map data values to a color it is better to use HoloViews' facilities for color mapping. Certain image-like types will apply colormapping automatically; e.g. for Image, QuadMesh or HeatMap types the first value dimension is automatically mapped to the color. In other cases the values to colormap can be declared by providing a color style option that specifies which dimension to map into the color value.

Named colormaps

HoloViews accepts colormaps specified either as an explicit list of hex or HTML colors, as a Matplotlib colormap object, or as the name of a bokeh, matplotlib, and colorcet palettes/colormap (which are available when the respective library is imported). The named colormaps available are listed here (suppressing the _r versions) and illustrated in detail in the separate Colormaps user guide:

In [12]:
def format_list(l):
    print(' '.join(sorted([k for k in l if not k.endswith('_r')])))

format_list(hv.plotting.list_cmaps())
Accent Blues Bokeh BrBG BuGn BuPu CMRmap Category10 Category20 Category20b Category20c Cividis Colorblind Dark2 GnBu Greens Greys Inferno Magma OrRd Oranges PRGn Paired Pastel1 Pastel2 PiYG Plasma PuBu PuBuGn PuOr PuRd Purples RdBu RdGy RdPu RdYlBu RdYlGn Reds Set1 Set2 Set3 Spectral Turbo Viridis Wistia YlGn YlGnBu YlOrBr YlOrRd afmhot autumn bgy bgyw binary bjy bkr bky blues bmw bmy bone brg bwr bwy cividis colorwheel cool coolwarm copper cubehelix cwr dimgray fire flag gist_earth gist_gray gist_heat gist_ncar gist_rainbow gist_stern gist_yarg glasbey glasbey_cool glasbey_dark glasbey_hv glasbey_light glasbey_warm gnuplot gnuplot2 gray gwv hot hsv inferno isolum jet kb kbc kg kgy kr magma nipy_spectral ocean pink plasma prism rainbow seismic spring summer tab10 tab20 tab20b tab20c terrain twilight twilight_shifted viridis winter

To use one of these colormaps simply refer to it by name with the cmap style option:

In [13]:
ls = np.linspace(0, 10, 400)
xx, yy = np.meshgrid(ls, ls)
bounds=(-1,-1,1,1)   # Coordinate system: (left, bottom, right, top)
img = hv.Image(np.sin(xx)*np.cos(yy), bounds=bounds).opts(colorbar=True, width=400)

img.relabel('PiYG').opts(cmap='PiYG') + img.relabel('PiYG_r').opts(cmap='PiYG_r')
Out[13]:

Custom colormaps

You can make your own custom colormaps by providing a list of hex colors:

In [14]:
img.relabel('Listed colors').opts(cmap=['#0000ff', '#8888ff', '#ffffff', '#ff8888', '#ff0000'], colorbar=True, width=400)
Out[14]: