Dynamic Map ¶
The Containers Tutorial introduced the HoloMap , a core HoloViews data structure that allows easy exploration of parameter spaces. The essence of a HoloMap is that it contains a collection of Elements (e.g. Images and Curves) that you can easily select and visualize.
HoloMaps hold fully constructed Elements at specifically sampled points in a multidimensional space. Although HoloMaps are useful for exploring highdimensional parameter spaces, they can very quickly consume huge amounts of memory to store all these Elements. For instance, a hundred samples along four orthogonal dimensions would need a HoloMap containing a hundred
million
Elements, each of which could be a substantial object that takes time to create and costs memory to store. Thus
HoloMaps
have some clear limitations:
 HoloMaps may require the generation of millions of Elements before the first element can be viewed.
 HoloMaps can easily exhaust all the memory available to Python.
 HoloMaps can even more easily exhaust all the memory in the browser when displayed.
 Static export of a notebook containing HoloMaps can result in impractically large HTML files.
The
DynamicMap
addresses these issues by computing and displaying elements dynamically, allowing exploration of much larger datasets:
 DynamicMaps generate elements on the fly, allowing the process of exploration to begin immediately.
 DynamicMaps do not require fixed sampling, allowing exploration of parameters with arbitrary resolution.
 DynamicMaps are lazy in the sense they only compute only as much data as the user wishes to explore.
Of course, these advantages come with some limitations:
 DynamicMaps require a live notebook server and cannot be fully exported to static HTML.
 DynamicMaps store only a portion of the underlying data, in the form of an Element cache, which reduces the utility of pickling a DynamicMap.
 DynamicMaps (and particularly their element caches) are typically stateful (with values that depend on patterns of user interaction), which can make them more difficult to reason about.
In order to handle the various situations in which one might want to use a
DynamicMap
,
DynamicMaps
can be defined in various "modes" that will be each described separately below:
 Bounded mode: All dimension ranges specified, allowing exploration within the ranges (i.e., within a bounded region of the multidimensional parameter space).

Sampled mode:
Some dimension ranges left unspecified, making the
DynamicMap
not viewable directly, but useful in combination with other objects or for later specifying samples or ranges.  Open mode: Dimension ranges not specified, with new elements created on request using a generator.
 Counter mode: Dimension ranges not specified, with new elements created based on a shared counter state, e.g. from a simulation.
All this will make much more sense once we've tried out some
DynamicMaps
and showed how they work, so let's create one!
This tutorial assumes that it will be run in a live notebook environment.
When viewed statically, DynamicMaps will only show the first available Element,
and will thus not have any slider widgets, making it difficult to follow the descriptions below.
It's also best to run this notebook one cell at a time, not via "Run All",
so that subsequent cells can reflect your dynamic interaction with widgets in previous cells.
DynamicMap
¶
Let's start by importing HoloViews and loading the extension:
import holoviews as hv
import numpy as np
hv.notebook_extension()
We will now create the
DynamicMap
equivalent of the
HoloMap
introduced in the
Containers Tutorial
. The
HoloMap
in that tutorial consisted of
Image
elements containing sine ring arrays as defined by the
sine_array
function:
x,y = np.mgrid[50:51, 50:51] * 0.1
def sine_array(phase, freq):
return np.sin(phase + (freq*x**2+freq*y**2))
This function returns NumPy arrays when called:
sine_array(0,1).shape
For a
DynamicMap
we will need a function that returns HoloViews elements. It is easy to modify the
sine_array
function so that it returns
Image
elements instead of raw arrays:
def sine_image(phase, freq):
return hv.Image(np.sin(phase + (freq*x**2+freq*y**2)))
sine_image(0,1) + sine_image(0.5,2)
Now we can demonstrate the first type of exploration enabled by a
DynamicMap
, called 'bounded' mode.
Bounded mode ¶
A 'bounded' mode
DynamicMap
is simply one where all the key dimensions have finite bounds. Bounded mode has the following properties:
 The limits of the space and/or the allowable values must be declared for all the key dimensions (unless sampled mode is also enabled).
 You can explore within the declared bounds at any resolution.

The data for the
DynamicMap
is defined using a callable that must be a function of its arguments (i.e., the output is strictly determined by the input arguments).
We can now create a DynamicMap by simply declaring the ranges of the two dimensions and passing the
sine_image
function as the
.data
:
dmap = hv.DynamicMap(sine_image, kdims=[hv.Dimension('phase',range=(0, np.pi)),
hv.Dimension('frequency', range=(0.01,np.pi))])
This object is created instantly, because it doesn't generate any
hv.Image
objects initially. We can now look at the printed representation of this object:
print(dmap)
All
DynamicMaps
will look similar, only differing in the listed dimensions. Now let's see how this dynamic map visualizes itself:
dmap
Here each
hv.Image
object visualizing a particular sine ring pattern with the given parameters is created dynamically, whenever the slider is set to that value. Any value in the allowable range can be requested, with a rough step size supported by sliding, and more precise values available by using the left and right arrow keys to change the value in small steps. In each case the new image is dynamically generated based on whatever the slider's values are.
As for any HoloViews Element, you can combine
DynamicMaps
to create a
Layout
using the
+
operator:
dmap + hv.DynamicMap(sine_image, kdims=[hv.Dimension('phase',range=(0, np.pi)),
hv.Dimension('frequency', range=(0.01,np.pi))])
As both elements are
DynamicMaps
with the same dimension ranges, the continuous sliders are retained. If one or more
HoloMaps
is used with a
DynamicMap
, the sliders will snap to the samples available in any
HoloMap
in the layout. For bounded
DynamicMaps
that do not require ranges to be declared, see
sampled mode
below.
If you are running this tutorial in a live notebook, the above cell should look like the
HoloMap
in the
Containers Tutorial
.
DynamicMap
is in fact a subclass of
HoloMap
with some crucial differences:
 You can now pick any value of phase or frequency up to the precision allowed by the slider.
 What you see in the cell above will not be exported in any HTML snapshot of the notebook.
Using your own callable ¶
You can use any callable to define a
DynamicMap
in closed mode. A valid
DynamicMap
is defined by the following criteria:
 There must be as many positional arguments in the callable signature as key dimensions.
 The argument order in the callable signature must match the order of the declared key dimensions.

All key dimensions are defined with a bounded
range
orvalues
parameter (for categorical dimensions).
Here is another example of a bounded
DynamicMap
:
def shapes(N, radius=0.5): # Positional keyword arguments are fine
paths = [hv.Path([[(radius*np.sin(a), radius*np.cos(a))
for a in np.linspace(np.pi, np.pi, n+2)]],
extents=(1,1,1,1))
for n in range(N,N+3)]
return hv.Overlay(paths)
%%opts Path (linewidth=1.5)
dmap = hv.DynamicMap(shapes, kdims=[hv.Dimension('N', range=(2,20)), hv.Dimension('radius', range=(0.5,1))])
dmap
As you can see, you can return
Overlays
from
DynamicMaps
, and
DynamicMaps
can be styled in exactly the same way as
HoloMaps
. Note that currently,
Overlay
objects should be returned from the callable itself; the
*
operator is not yet supported at the
DynamicMap
level (i.e., between a
DynamicMap
and other Elements).
%opts Path (linewidth=1.5)
The
DynamicMap
cache
¶
Above we mentioned that
DynamicMap
is an instance of
HoloMap
. Does this mean it has a
.data
attribute?
dmap.data
This is exactly the same sort of
.data
as the equivalent
HoloMap
, except that this value will vary according to how much you explored the parameter space of
dmap
using the sliders above. In a
HoloMap
,
.data
contains a defined sampling along the different dimensions, whereas in a
DynamicMap
, the
.data
is simply the
cache
.
The cache serves two purposes:
 Avoids recomputation of an element should we revisit a particular point in the parameter space. This works well for categorical or integer dimensions, but doesn't help much when using continuous sliders for realvalued dimensions.

Records the space that has been explored with the
DynamicMap
for any later conversion to aHoloMap
.  Ensures that we store only a finite history of generator output when using open mode together with infinite generators.
We can always convert
any
DynamicMap
directly to a
HoloMap
as follows:
hv.HoloMap(dmap)
This is in fact equivalent to declaring a HoloMap with the same parameters (dimensions, etc.) using
dmap.data
as input, but is more convenient.
Although creating a HoloMap this way is easy, the result is poorly controlled, as the keys in the HoloMap are defined by how you moved the sliders around. For instance, if you run this tutorial straight through using
Run All
, the above cell won't have any sliders at all, because only a single element will be in the dmap's cache until the
DynamicMap
s sliders are dragged.
If you instead want to specify a specific set of samples, you can easily do so by using the same keyselection semantics as for a
HoloMap
to define exactly which elements are initially sampled in the cache:
dmap[{(2,0.5), (2,1.0), (3,0.5), (3,1.0)}] # Returns a *new* DynamicMap with the specified keys in its cache
This object behaves the same way as before it was sampled, but now this
DynamicMap
can now be exported to static HTML with the allowed slider positions as specified in the cache, without even having to cast to a
HoloMap
. Of course, if the intent is primarily to have something statically exportable, then it's still a good idea to explicitly cast it to a
HoloMap
so that it will clearly contain only a finite set of Elements.
The key selection above happens to define a Cartesian product, which is one of the most common way to sample across dimensions. Because the list of such dimension values can quickly get very large when enumerated as above, we provide a way to specify a Cartesian product directly, which also works with
HoloMaps
. Here is an equivalent way of defining the same set of four points in that twodimensional space:
dmap[{2,3},{0.5,1.0}]
dmap.data
Note that you can index a
DynamicMap
with a literal key in exactly the same way as a
HoloMap
. If the key exists in the cache, it is returned directly, otherwise a suitable element will be generated. Here is an example of how you can access the last key in the cache, creating such an element if it didn't already exist:
dmap[dmap.keys()[1]]
The default cache size of 500 Elements is relatively high so that interactive exploration will work smoothly, but you can reduce it using the
cache_size
parameter if you find you are running into issues with memory consumption. A bounded
DynamicMap
with
cache_size=1
requires the least memory, but will recompute a new Element every time the sliders are moved, making it less responsive.
Slicing bounded
DynamicMaps
¶
The declared dimension ranges define the absolute limits allowed for exploration in a bounded
DynamicMap
. That said, you can use the
soft_range
parameter to view subregions within that range. Setting the
soft_range
parameter on dimensions can be done conveniently using slicing on bounded
DynamicMaps
:
sliced = dmap[4:8, :]
sliced
Notice that N is now restricted to the range 4:8. Open slices are used to release any
soft_range
values, which resets the limits back to those defined by the full range:
sliced[:, 0.8:1.0]
The
[:]
slice leaves the soft_range values alone and can be used as a convenient way to clone a
DynamicMap
. Note that mixing slices with any other object type is not supported. In other words, once you use a single slice, you can only use slices in that indexing operation.
Sampling DynamicMaps ¶
We have now seen one way of sampling a
DynamicMap
, which is to populate the cache with a set of keys. This approach is designed to make conversion of a
DynamicMap
into a
HoloMap
easy. One disadvantage of this type of sampling is that populating the cache consumes memory, resulting in many of the same limitations as
HoloMap
. To avoid this, there are two other ways of sampling a bounded
DynamicMap
:
Dimension values ¶
If you want a fixed sampling instead of continuous sliders, yet still wish to retain the online generation of elements as the sliders are moved, you can simply declare the dimension to have a fixed list of values. The result appears to the user just like a
HoloMap
, and will show the same data as the precached version above, but now generates the data dynamically to reduce memory requirements and speed up the initial display:
hv.DynamicMap(shapes, kdims=[hv.Dimension('N', values=[2,3,4,5]),
hv.Dimension('radius', values=[0.7,0.8,0.9,1])])
Sampled mode ¶
A bounded
DynamicMap
in
sampled
mode is the least restricted type of
DynamicMap
, as it can be declared without any information about the allowable dimension ranges or values:
dmap = hv.DynamicMap(shapes, kdims=['N', 'radius'], sampled=True)
dmap
As you can see, this type of
DynamicMap
cannot be visualized in isolation, because there are no values or ranges specified for the dimensions. You could view it by explicitly specifying such values to get cached samples (and can then cast it to a HoloMap if desired):
dmap[{2,3},{0.5,1.0}]
Still, on its own, a sampledmode
DynamicMap
may not seem very useful. When they become very convenient is when combined with one or more
HoloMaps
in a
Layout
. Because a sampled
DynamicMap
doesn't have explicitly declared dimension ranges, it can always adopt the set of sample values from
HoloMaps
in the layout.
dmap + hv.HoloMap({(N,r):shapes(N, r) for N in [3,4,5] for r in [0.5,0.75]}, kdims=['N', 'radius'])
In this way, you only need to worry about choosing specific values or ranges for your dimensions for a single (
HoloMap
) object, with the others left openended as sampledmode
DynamicMaps
. This convenience is subject to three particular restrictions:

Sampled
DynamicMaps
do not visualize themselves in isolation (as we have already seen). 
You cannot build a layout consisting of sampledmode
DynamicMaps
only, because at least one HoloMap is needed to define the samples. 
There currently cannot be more dimensions declared in the sampled
DynamicMap
than across the rest of the layout. We hope to relax this restriction in future.
Using groupby to discretize a DynamicMap ¶
A DynamicMap also makes it easy to partially or completely discretize a function to evaluate in a complex plot. By grouping over specific dimensions that define a fixed sampling via the Dimension values parameter, the DynamicMap can be viewed as a
GridSpace
,
NdLayout
, or
NdOverlay
. If a dimension specifies only a continuous range it can't be grouped over, but it may still be explored using the widgets. This means we can plot partial or completely discretized views of a parameter space easily.
Partially discretize ¶
The implementation for all the groupby operations uses the
.groupby
method internally, but we also provide three higherlevel convenience methods to group dimensions into an
NdOverlay
(
.overlay
),
GridSpace
(
.grid
), or
NdLayout
(
.layout
).
Here we will evaluate a simple sine function with three dimensions, the phase, frequency, and amplitude. We assign the frequency and amplitude discrete samples, while defining a continuous range for the phase:
xs = np.linspace(0, 2*np.pi)
def sin(ph, f, amp):
return hv.Curve((xs, np.sin(xs*f+ph)*amp))
kdims=[hv.Dimension('phase', range=(0, np.pi)),
hv.Dimension('frequency', values=[0.1, 1, 2, 5, 10]),
hv.Dimension('amplitude', values=[0.5, 5, 10])]
sine_dmap = hv.DynamicMap(sin, kdims=kdims)
Next we define the amplitude dimension to be overlaid and the frequency dimension to be gridded:
%%opts GridSpace [show_legend=True fig_size=200]
sine_dmap.overlay('amplitude').grid('frequency')
As you can see, instead of having three sliders (one per dimension), we've now laid out the frequency dimension as a discrete set of values in a grid, and the amplitude dimension as a discrete set of values in an overlay, leaving one slider for the remaining dimension (phase). This approach can help you visualize a large, multidimensional space efficiently, with full control over how each dimension is made visible.
Fully discretize ¶
Given a continuous function defined over a space, we could sample it manually, but here we'll look at an example of evaluating it using the groupby method. Let's look at a spiral function with a frequency and first and secondorder phase terms. Then we define the dimension values for all the parameters and declare the DynamicMap:
%opts Path (linewidth=1 color=Palette('Blues'))
def spiral_equation(f, ph, ph2):
r = np.arange(0, 1, 0.005)
xs, ys = (r * fn(f*np.pi*np.sin(r+ph)+ph2) for fn in (np.cos, np.sin))
return hv.Path((xs, ys))
kdims=[hv.Dimension('f', values=list(np.linspace(1, 10, 10))),
hv.Dimension('ph', values=list(np.linspace(0, np.pi, 10))),
hv.Dimension('ph2', values=list(np.linspace(0, np.pi, 4)))]
spiral_dmap = hv.DynamicMap(spiral_equation, kdims=kdims)
Now we can make use of the
.groupby
method to group over the frequency and phase dimensions, which we will display as part of a GridSpace by setting the
container_type
. This leaves the second phase variable, which we assign to an NdOverlay by setting the
group_type
:
%%opts GridSpace [xaxis=None yaxis=None] Path [bgcolor='w' xaxis=None yaxis=None]
spiral_dmap.groupby(['f', 'ph'], group_type=hv.NdOverlay, container_type=hv.GridSpace)
This grid shows a range of frequencies
f
on the x axis, a range of the first phase variable
ph
on the
y
axis, and a range of different
ph2
phases as overlays within each location in the grid. As you can see, these techniques can help you visualize multidimensional parameter spaces compactly and conveniently.
Open mode ¶
DynamicMap
also allows unconstrained exploration over unbounded dimensions in 'open' mode. There are two key differences between open mode and bounded mode:

Instead of a callable, the input to an open
DynamicMap
is a generator. Once created, the generator is only used vianext()
.  At least one of the declared key dimensions must have an unbounded range (i.e., with an upper or lower bound not specified).

An open mode
DynamicMap
can run forever, or until aStopIteration
exception is raised. 
Open mode
DynamicMaps
can be stateful, with an irreversible direction of time.
Infinite generators ¶
Our first example will be using an infinite generator which plots the histogram for a given number of random samples drawn from a Gaussian distribution:
def gaussian_histogram(samples, scale):
frequencies, edges = np.histogram([np.random.normal(scale=scale)
for i in range(samples)], 20)
return hv.Histogram(frequencies, edges).relabel('Gaussian distribution')
gaussian_histogram(100,1) + gaussian_histogram(150,1.5)
Lets now use this in the following generator:
def gaussian_sampler(samples=10, delta=10, scale=1.0):
np.random.seed(1)
while True:
yield gaussian_histogram(samples, scale)
samples+=delta
gaussian_sampler()
Which allows us to define the following infinite
DynamicMap
:
dmap = hv.DynamicMap(gaussian_sampler(), kdims=['step'])
dmap
Note that step is shown as an integer. This is the default behavior and corresponds to the call count (i.e the number of times
next()
has been called on the generator. If we want to show the actual number of samples properly, we need our generator to return a (key, element) pair:
def gaussian_sampler_kv(samples=10, delta=10, scale=1.0):
np.random.seed(1)
while True:
yield (samples, gaussian_histogram(samples, scale))
samples+=delta
hv.DynamicMap(gaussian_sampler_kv(), kdims=['samples'])
Note that if you pause the
DynamicMap
, you can scrub back to previous frames in the cache. In other words, you can view a limited history of elements already output by the generator, which does
not
reexecute the generator in any way (as it is indeed impossible to rewind generator state). If you have a stateful generator that, say, depends on the current wind speed in Scotland, this history may be misleading, in which case you can simply set the
cache_size
parameter to 1.
Multidimensional generators ¶
In open mode, elements are naturally serialized by a linear sequence of
next()
calls, yet multiple key dimensions can still be defined:
def gaussian_sampler_2D(samples=10, scale=1.0, delta=10):
np.random.seed(1)
while True:
yield ((samples, scale), gaussian_histogram(samples, scale))
samples=(samples + delta) if scale==2 else samples
scale = 2 if scale == 1 else 1
dmap = hv.DynamicMap(gaussian_sampler_2D(), kdims=['samples', 'scale'])
dmap
Here we bin the histogram for two different scale values. Above we can visualize this linear sequence of
next()
calls, but by casting this open map to a
HoloMap
, we can obtain a multidimensional parameter space that we can freely explore:
hv.HoloMap(dmap)
Note that if you ran this notebook using
Run All
, only a single frame will be available in the above cell, with no sliders, but if you ran it interactively and viewed a range of values in the previous cell, you'll have multiple sliders in this cell allowing you to explore whatever range of frames is in the cache from the previous cell.
Finite generators ¶
Open mode
DynamicMaps
are finite and terminate if
StopIteration
is raised. This example terminates when the means of two sets of gaussian samples fall within a certain distance of each other:
def sample_distributions(samples=10, delta=50, tol=0.04):
np.random.seed(42)
while True:
gauss1 = np.random.normal(size=samples)
gauss2 = np.random.normal(size=samples)
data = (['A']*samples + ['B']*samples, np.hstack([gauss1, gauss2]))
diff = abs(gauss1.mean()  gauss2.mean())
if abs(gauss1.mean()  gauss2.mean()) > tol:
yield ((samples, diff), hv.BoxWhisker(data, kdims=['Group'], vdims=['Value']))
else:
raise StopIteration
samples+=delta
dmap = hv.DynamicMap(sample_distributions(), kdims=['samples', '$\delta$'])
dmap
Now if you are familiar with generators in Python, you might be wondering what happens when a finite generator is exhausted. First we should mention that casting a
DynamicMap
to a list is always finite, because
__iter__
returns the cache instead of a potentially infinite generator:
list(dmap) # The cache
As we know this
DynamicMap
is finite, we can make sure it is exhausted as follows:
while True:
try:
next(dmap) # Returns Image elements
except StopIteration:
print("The dynamic map is exhausted.")
break
Now let's have a look at the dynamic map:
dmap
Here, we are given only the textbased representation, to indicate that the generator is exhausted. However, as the process of iteration has populated the cache, we can still view the output as a
HoloMap
using
hv.HoloMap(dmap)
as before.
Counter mode and temporal state ¶
Open mode is intended to use live data streams or ongoing simulations with HoloViews. The
DynamicMap
will generate live visualizations for as long as new data is requested. Although this works for simple cases, Python generators have problematic limitations that can be resolved using 'counter' mode.
In this example, let's say we have a simulation or data recording where time increases in integer steps:
def time_gen(time=1):
while True:
yield time
time += 1
time = time_gen()
Now let's create two generators that return Images that are a function of the simulation time. Here, they have identical output except one of the outputs includes additive noise:
ls = np.linspace(0, 10, 200)
xx, yy = np.meshgrid(ls, ls)
def cells():
while True:
t = next(time)
arr = np.sin(xx+t)*np.cos(yy+t)
yield hv.Image(arr)
def cells_noisy():
while True:
t = next(time)
arr = np.sin(xx+t)*np.cos(yy+t)
yield hv.Image(arr + 0.2*np.random.rand(200,200))
Now let's create a Layout using these two generators:
hv.DynamicMap(cells(), kdims=['time']) + hv.DynamicMap(cells_noisy(), kdims=['time'])
If you pause the animation, you'll see that these two outputs are not in phase, despite the fact that the generators are defined identically (modulo the additive noise)!
The issue is that generators are used via the
next()
interface, and so when either generator is called, the simulation time is increased. In other words, the noisy version in subfigure
B
actually corresponds to a later time than in subfigure
A
.
This is a fundamental issue, as the
next
method does not take arguments. What we want is for all the
DynamicMaps
presented in a Layout to share a common simulation time, which is only incremented by interaction with the scrubber widget. This is exactly the sort of situation where you want to use counter mode.
Handling timedependent state ¶
To define a
DynamicMap
in counter mode:
 Leave one or more dimensions unbounded (as in open mode)
 Supply a callable (as in bounded mode) that accepts one argument
This callable should act in the same way as the generators of open mode, except the output is controlled by the single counter argument.
ls = np.linspace(0, 10, 200)
xx, yy = np.meshgrid(ls, ls)
def cells_counter(t):
arr = np.sin(xx+t)*np.cos(yy+t)
return hv.Image(arr)
def cells_noisy_counter(t):
arr = np.sin(xx+t)*np.cos(yy+t)
return hv.Image(arr + 0.2*np.random.rand(200,200))
Now if we supply these functions instead of generators, A and B will correctly be in phase:
hv.DynamicMap(cells_counter, kdims=['time']) + hv.DynamicMap(cells_noisy_counter, kdims=['time'])
Unfortunately, an integer counter is often too simple to describe simulation time, which may be a float with realworld units. To address this, we can simply return the actual key values we want along the time dimension, just as was demonstrated in open mode using generators:
ls = np.linspace(0, 10, 200)
xx, yy = np.meshgrid(ls, ls)
# Example of a global simulation time
# typical in many applications
t = 0
def cells_counter_kv(c):
global t
t = 0.1 * c
arr = np.sin(xx+t)*np.cos(yy+t)
return (t, hv.Image(arr))
def cells_noisy_counter_kv(c):
global t
t = 0.1 * c
arr = np.sin(xx+t)*np.cos(yy+t)
return (t, hv.Image(arr + 0.2*np.random.rand(200,200)))
hv.DynamicMap(cells_counter_kv, kdims=['time']) + hv.DynamicMap(cells_noisy_counter_kv, kdims=['time'])
print("The global simulation time is now t=%f" % t)
Ensuring that the HoloViews counter maps to a suitable simulation time is the responsibility of the user. However, once a consistent scheme is configured, the callable in each
DynamicMap
can specify the desired simulation time. If the requested simulation time is the same as the current simulation time, nothing needs to happen. Otherwise, the simulator can be run forward by the requested amount. In this way, HoloViews can provide a rich graphical interface for controlling and visualizing an external simulator, with very little code required.
Slicing in open and counter mode ¶
Slicing open and counter mode
DynamicMaps
has the exact same semantics as normal
HoloMap
slicing, except now the
.data
attribute corresponds to the cache. For instance:
def sine_kv_gen(phase=0, freq=0.5):
while True:
yield (phase, hv.Image(np.sin(phase + (freq*x**2+freq*y**2))))
phase+=0.2
dmap = hv.DynamicMap(sine_kv_gen(), kdims=['phase'])
Let's fill the cache with some elements:
for i in range(21):
dmap.next()
print("Min key value in cache:%s\nMax key value in cache:%s" % (min(dmap.keys()), max(dmap.keys())))
sliced = dmap[1:3.1]
print("Min key value in cache:%s\nMax key value in cache:%s" % (min(sliced.keys()), max(sliced.keys())))
DynamicMaps and normalization ¶
By default, a
HoloMap
normalizes the display of elements according the minimum and maximum values found across the
HoloMap
. This automatic behavior is not possible in a
DynamicMap
, where arbitrary new elements are being generated on the fly. Consider the following examples where the arrays contained within the returned
Image
objects are scaled with time:
%%opts Image {+axiswise}
ls = np.linspace(0, 10, 200)
xx, yy = np.meshgrid(ls, ls)
def cells(vrange=False):
"The range is set on the value dimension when vrange is True "
time = time_gen()
while True:
t = next(time)
arr = t*np.sin(xx+t)*np.cos(yy+t)
vdims=[hv.Dimension('Intensity', range=(0,10))] if vrange else ['Intensity']
yield hv.Image(arr, vdims=vdims)
hv.DynamicMap(cells(vrange=False), kdims=['time']) + hv.DynamicMap(cells(vrange=True), kdims=['time'])
Here we use
+axiswise
to see the behavior of the two cases independently. We see in
A
that when
vrange=False
and no range is set on the value dimension, no automatic normalization occurs (unlike a
HoloMap
). In
B
we see that normalization is applied, but only when the value dimension range has been specified.
In other words,
DynamicMaps
cannot support automatic normalization across their elements, but do support the same explicit normalization behavior as
HoloMaps
. Values that are generated outside this range are simply clipped according the usual semantics of explicit value dimension ranges.
Note that we can always have the option of casting a
DynamicMap
to a
HoloMap
in order to automatically normalize across the cached values, without needing explicit value dimension ranges.
Using DynamicMaps in your code ¶
As you can see,
DynamicMaps
let you use HoloViews with a very wide range of dynamic data formats and sources, making it simple to visualize ongoing processes or very large data spaces.
Download this notebook from GitHub (rightclick to download).