Contour plots#
Contour plots are used to calculate and render lines of constant value in two-dimensional quadrilateral grids. Both the lines and the filled regions between lines can be rendered with a single function call.
Simple example#
Here is a simple example rendering both contour lines and filled polygon regions.
import numpy as np
from bokeh.palettes import Sunset8
from bokeh.plotting import figure, show
# Data to contour is the sum of two Gaussian functions.
x, y = np.meshgrid(np.linspace(0, 3, 40), np.linspace(0, 2, 30))
z = 1.3*np.exp(-2.5*((x-1.3)**2 + (y-0.8)**2)) - 1.2*np.exp(-2*((x-1.8)**2 + (y-1.3)**2))
p = figure(width=550, height=300, x_range=(0, 3), y_range=(0, 2))
levels = np.linspace(-1, 1, 9)
contour_renderer = p.contour(x, y, z, levels, fill_color=Sunset8, line_color="black")
colorbar = contour_renderer.construct_color_bar()
p.add_layout(colorbar, "right")
show(p)
By convention, z
is the 2D array to contour and is defined on an x
,
y
grid. Here the grid is a regularly-spaced Cartesian grid. levels
contains the sequence of levels to contour.
Both line_color
and fill_color
are optional keyword arguments to
contour()
. line_color
must be specified to
draw contour lines and fill_color
to draw filled contour polygons. They
can be scalar or vector visual properties.
Note
The length of vector visual properties for contour lines is len(levels)
and for filled contour polygons it is len(levels)-1
.
In this example line_color
is a scalar so every contour line is rendered
as a solid black line. The fill_color
is a vector so that the filled
regions between contour levels are rendered with different colors.
There is a colorbar on the right of the plot which is obtained using
construct_color_bar()
. It automatically
displays the same fill and line visual properties as the contour plot.
Polar grid example#
Here is a more complicated example showing other features available for contour plots.
import numpy as np
from bokeh.palettes import Cividis
from bokeh.plotting import figure, show
# Data to contour is a 2D sin wave on a polar grid.
radius, angle = np.meshgrid(np.linspace(0, 1, 20), np.linspace(0, 2*np.pi, 120))
x = radius*np.cos(angle)
y = radius*np.sin(angle)
z = 1 + np.sin(3*angle)*np.sin(np.pi*radius)
p = figure(width=550, height=400)
levels = np.linspace(0, 2, 11)
contour_renderer = p.contour(
x=x, y=y, z=z, levels=levels,
fill_color=Cividis,
hatch_pattern=["x"]*5 + [" "]*5,
hatch_color="white",
hatch_alpha=0.5,
line_color=["white"]*5 + ["black"] + ["red"]*5,
line_dash=["solid"]*6 + ["dashed"]*5,
line_width=[1]*6 + [2]*5,
)
colorbar = contour_renderer.construct_color_bar(title="Colorbar title")
p.add_layout(colorbar, "right")
show(p)
The grid is polar, wrapping around on itself, and there are many more visual
properties including line
, fill
and hatch
properties. Many of
these are vector properties so that it is possible to emphasize, for example,
positive and negative contour levels differently.
All visual properties can be scalar or vector of the correct length. Color
visual properties line_color
, fill_color
and hatch_color
support a
few extra options for how they can be specified:
A sequence of colors that is longer or shorter than required will be resampled using
interp_palette()
. The length 256 palettes such asCividis256
are useful here.A palette collection such as
Cividis
may be used, which is a dictionary that maps from palette length (number of colors) to palette. If the collection contains a palette of the correct length then that is used. If the required length is outside of those available in the collection then the palette with the nearest length is used, linearly interpolated.
construct_color_bar()
accepts other
keyword arguments that are passed to the
ContourColorBar
constructor to set properties such as
the title
shown here.
Animated contours#
Bokeh can generate animated contour plots using bokeh serve
as the contour
calculations occur in Python. Here is an example taken from
examples/app/contour_animated.py
:
import numpy as np
from bokeh.driving import count
from bokeh.palettes import PiYG
from bokeh.plotting import curdoc, figure
from bokeh.plotting.contour import contour_data
x, y = np.meshgrid(np.linspace(-1, 1, 41), np.linspace(-1, 1, 41))
levels = np.linspace(-1.0, 1.0, 11)
def get_z(timestep):
delta = 0.08*np.cos(timestep*0.15)
amps = [0.95, 0.95, -0.95, -0.95]
xmids = [-0.4, 0.4, -0.4, 0.4]
ymids = [-0.4, 0.4, 0.4, -0.4]
rads = [0.4 + delta, 0.4 + delta, 0.4 - delta, 0.4 - delta]
z = np.zeros_like(x)
for amp, xmid, ymid, rad in zip(amps, xmids, ymids, rads):
z += amp*np.exp( -((x-xmid)**2 + (y-ymid)**2)/rad**2 )
return z
@count()
def callback(timestep):
z = get_z(timestep)
new_contour_data = contour_data(x, y, z, levels)
contour_renderer.set_data(new_contour_data)
fig = figure(width=600, height=450)
contour_renderer = fig.contour(x, y, get_z(0), levels, fill_color=PiYG,
line_color="black", line_width=[1]*5 + [3] + [1]*5)
colorbar = contour_renderer.construct_color_bar()
fig.add_layout(colorbar, 'right')
curdoc().add_periodic_callback(callback, 40)
curdoc().add_root(fig)
To run this on a Bokeh server use
bokeh serve --show contour_animated.py
The key sequence of actions to perform the animation are:
Call
contour()
as usual, and store the returnedContourRenderer
.Determine the updated
z
array, which might be read from file or calculated, for example.Pass the updated
z
and unchangedx
,y
andlevels
tocontour_data()
to generate a contour data object.Call
set_data()
with the new contour data object.Repeat from stage 2.
The animation example here assumes the grid, contour levels and visual properties are not changed. It is possible to do so, but care is needed to correctly deal with changing plot bounds and assignment of visual properties to contour levels, so it is usually easier to remove the old unwanted contour plot and replace it with a new one in these circumstances.
Advanced details#
The only compulsory keyword arguments to contour()
are z
, levels
and at least one of fill_color
and line_color
.
x
and y
are optional and if not specified a Cartesian grid will be
used with a grid spacing of 1 in both directions.
To exclude grid points from the contour calculation then either use a NumPy
masked array for z
with the excluded grid points masked out, or set the
z
values of those grid points to np.nan
.
Contour lines are implemented using a MultiLine
glyph and filled contour polygons as a MultiPolygons
glyph with the line_width
set to zero.
The calculation of contours is performed by ContourPy
. For information
about this see the ContourPy documentation.
Note
Contouring was added to Bokeh version 3.0 and improvements are planned for future releases.