Plotting with basic glyphs#
Creating figures#
Bokeh plots you create with the bokeh.plotting interface come with a default set of tools and visual styles. For information on how to customize the visual style of plots, see Styling visual attributes. For information about changing or specifying tools, see Configuring plot tools.
Scatter markers#
Bokeh includes a large variety of markers for creating scatter plots. For
example, to render circle scatter markers on a plot, use the circle()
method of
figure()
:
from bokeh.plotting import figure, output_file, show
# output to static HTML file
output_file("line.html")
p = figure(width=400, height=400)
# add a circle renderer with a size, color, and alpha
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
# show the results
show(p)
Similarly, use the square()
method of figure()
to scatter square markers
on a plot:
from bokeh.plotting import figure, output_file, show
# output to static HTML file
output_file("square.html")
p = figure(width=400, height=400)
# add a square renderer with a size, color, and alpha
p.square([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="olive", alpha=0.5)
# show the results
show(p)
Bokeh’s built-in scatter markers consist of a set of base markers, most of which can be combined with different kinds of additional visual features. This is an overview of all available scatter markers:
To see details and example plots for any of the available scatter markers, click on the corresponding glyph method in the following list:
All the markers have the same set of properties: x
, y
, size
(in
screen units), and angle
(in radians by default). The circle()
marker is
an exception: this method accepts an additional radius
property that you can
use with data units.
Line glyphs#
Single lines#
The example below shows how to generate a single line glyph from
one-dimensional sequences of x
and y
points using the line()
glyph
method:
from bokeh.plotting import figure, output_file, show
output_file("line.html")
p = figure(width=400, height=400)
# add a line renderer
p.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=2)
show(p)
Step lines#
For some kinds of data, discrete steps between data points may work better than
linear segments. To produce this type of data representation, use the step()
glyph method.
from bokeh.plotting import figure, output_file, show
output_file("line.html")
p = figure(width=400, height=400)
# add a steps renderer
p.step([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=2, mode="center")
show(p)
Adjust the mode
parameter to draw step levels with the x-coordinates
before, after, or in the middle of each step.
Multiple lines#
If you want to draw multiple lines in one go, use the multi_line()
glyph
method as follows:
from bokeh.plotting import figure, output_file, show
output_file("patch.html")
p = figure(width=400, height=400)
p.multi_line([[1, 3, 2], [3, 4, 6, 6]], [[2, 1, 4], [4, 7, 8, 5]],
color=["firebrick", "navy"], alpha=[0.8, 0.3], line_width=4)
show(p)
Note
Unlike many other glyph methods, multi_line()
accepts a list of lists of
x
and y
positions for each line. The multi_line()
method also
expects a scalar value or a list of scalars for each line for parameters
such as color, alpha, and line width. You can similarly use a
ColumnDataSource
consisting of a list of lists of point coordinates
and a list of scalar values of matching length.
Missing points#
You can pass NaN
values to line()
and multi_line()
glyphs. This produces
disjointed lines with gaps for NaN
values.
from bokeh.plotting import figure, output_file, show
output_file("line.html")
p = figure(width=400, height=400)
# add a line renderer with a NaN
nan = float('nan')
p.line([1, 2, 3, nan, 4, 5], [6, 7, 2, 4, 4, 5], line_width=2)
show(p)
Stacked lines#
You may wish to stack lines with a common index when working with time series
of percentages and other similar data. To do so, you can use the vline_stack()
and hline_stack()
convenience methods.
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show
output_file("vline_stack.html")
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y1=[1, 2, 4, 3, 4],
y2=[1, 4, 2, 2, 3],
))
p = figure(width=400, height=400)
p.vline_stack(['y1', 'y2'], x='x', source=source)
show(p)
Note
This and other examples in this chapter rely on `ColumnDataSource
for
data structuring. For information on how to work with this data structure,
see Providing data.
Bars and rectangles#
Bars#
To make drawing rectangular bars more convenient, Bokeh provides hbar()
and
vbar()
glyph functions that combine the coordinate systems above.
To draw vertical bars by specifying a center x-coordinate, width, and top and
bottom endpoints, use the vbar()
glyph function:
from bokeh.plotting import figure, output_file, show
output_file('vbar.html')
p = figure(width=400, height=400)
p.vbar(x=[1, 2, 3], width=0.5, bottom=0,
top=[1.2, 2.5, 3.7], color="firebrick")
show(p)
To draw horizontal bars by specifying a center y-coordinate, height, and left
and right endpoints, use the hbar()
glyph function:
from bokeh.plotting import figure, output_file, show
output_file('hbar.html')
p = figure(width=400, height=400)
p.hbar(y=[1, 2, 3], height=0.5, left=0,
right=[1.2, 2.5, 3.7], color="navy")
show(p)
Stacked bars#
To stack the bars, you can use the vbar_stack()
and hbar_stack()
convenience
methods.
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show
output_file("hbar_stack.html")
source = ColumnDataSource(data=dict(
y=[1, 2, 3, 4, 5],
x1=[1, 2, 4, 3, 4],
x2=[1, 4, 2, 2, 3],
))
p = figure(width=400, height=400)
p.hbar_stack(['x1', 'x2'], y='y', height=0.8, color=("grey", "lightgrey"), source=source)
show(p)
For more examples of stacked bars, see Handling categorical data.
Rectangles#
To draw axis aligned rectangles by specifying the left
, right
,
top
, and bottom
positions, use the quad()
glyph function:
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.quad(top=[2, 3, 4], bottom=[1, 2, 3], left=[1, 2, 3],
right=[1.2, 2.5, 3.7], color="#B3DE69")
show(p)
To draw axis aligned rectangles by specifying the x
and y
coordinates for a corner, and a width
and height
, use the block()
glyph function:
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.block(x=[1, 2, 3], y=[1, 2, 3], width=[0.2, 0.5, 0.1], height=1.5)
show(p)
To draw arbitrary rectangles by specifying center coordinates, width
,
height
, and angle
, use the rect()
glyph function:
from math import pi
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.rect(x=[1, 2, 3], y=[1, 2, 3], width=0.2, height=40, color="#CAB2D6",
angle=pi/3, height_units="screen")
show(p)
Hex tiles#
Bokeh can plot hexagonal tiles, which you can use to show binned aggregations
and more. The hex_tile()
method takes a size
parameter to define the size of the hex grid and axial coordinates to
specify the tiles.
import numpy as np
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.util.hex import axial_to_cartesian
output_file("hex_coords.html")
q = np.array([0, 0, 0, -1, -1, 1, 1])
r = np.array([0, -1, 1, 0, 1, -1, 0])
p = figure(width=400, height=400, toolbar_location=None)
p.grid.visible = False
p.hex_tile(q, r, size=1, fill_color=["firebrick"]*3 + ["navy"]*4,
line_color="white", alpha=0.5)
x, y = axial_to_cartesian(q, r, 1, "pointytop")
p.text(x, y, text=["(%d, %d)" % (q,r) for (q, r) in zip(q, r)],
text_baseline="middle", text_align="center")
show(p)
A more practical example below computes counts per bin using the
hexbin()
function and plots the color mapped counts.
import numpy as np
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.transform import linear_cmap
from bokeh.util.hex import hexbin
n = 50000
x = np.random.standard_normal(n)
y = np.random.standard_normal(n)
bins = hexbin(x, y, 0.1)
p = figure(tools="wheel_zoom,reset", match_aspect=True, background_fill_color='#440154')
p.grid.visible = False
p.hex_tile(q="q", r="r", size=0.1, line_color=None, source=bins,
fill_color=linear_cmap('counts', 'Viridis256', 0, max(bins.counts)))
output_file("hex_tile.html")
show(p)
You can simplify this code by calling the hexbin()
method of figure()
.
Directed areas#
Directed areas are filled regions between two series that share a common index.
For instance, a vertical directed area has one x
coordinate array and two
y
coordinate arrays, y1
and y2
, defining the space for Bokeh to
fill.
Single areas#
To fill an area in vertical direction, use the varea()
method. You can do the
same in horizontal direction with harea()
.
from bokeh.plotting import figure, output_file, show
output_file("varea.html")
p = figure(width=400, height=400)
p.varea(x=[1, 2, 3, 4, 5],
y1=[2, 6, 4, 3, 5],
y2=[1, 4, 2, 2, 3])
show(p)
Stacked areas#
To stack directed areas, use the varea_stack()
and harea_stack()
convenience
methods.
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show
output_file("varea_stack.html")
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y1=[1, 2, 4, 3, 4],
y2=[1, 4, 2, 2, 3],
))
p = figure(width=400, height=400)
p.varea_stack(['y1', 'y2'], x='x', color=("grey", "lightgrey"), source=source)
show(p)
Patches and polygons#
Single patches#
The following example generates a single polygonal patch from one-dimensional
sequences of x
and y
points using the patch()
glyph method:
from bokeh.plotting import figure, output_file, show
output_file("patch.html")
p = figure(width=400, height=400)
# add a patch renderer with an alpha and line width
p.patch([1, 2, 3, 4, 5], [6, 7, 8, 7, 3], alpha=0.5, line_width=2)
show(p)
Multiple patches#
To plot several polygonal patches, use the patches()
glyph method:
from bokeh.plotting import figure, output_file, show
output_file("patch.html")
p = figure(width=400, height=400)
p.patches([[1, 3, 2], [3, 4, 6, 6]], [[2, 1, 4], [4, 7, 8, 5]],
color=["firebrick", "navy"], alpha=[0.8, 0.3], line_width=2)
show(p)
Note
Unlike many other glyph methods, patches()
accepts a list of lists of x
and y
positions for each line. The patches()
method also expects a
scalar value or a list of scalars for each patch for parameters such as
color, alpha, and line width. You can similarly use a ColumnDataSource
consisting of a list of lists of point coordinates and a list of scalar
values of matching length.
Missing points#
Just as with the line()
and multi_line()
methods, you can pass NaN
values
to patch()
and patches()
glyphs. This produces disjointed patches with gaps
for NaN
values.
from bokeh.plotting import figure, output_file, show
output_file("patch.html")
p = figure(width=400, height=400)
# add a patch renderer with a NaN value
nan = float('nan')
p.patch([1, 2, 3, nan, 4, 5, 6], [6, 7, 5, nan, 7, 3, 6], alpha=0.5, line_width=2)
show(p)
Warning
Bokeh doesn’t currently support hit testing on patch objects with NaN
values.
Polygons#
The multi_polygons()
glyph uses nesting to accept a variety of information
relevant to polygons. The method duplicates the functionality of patches()
but
you can also use it to render holes inside polygons.
Note
Unlike many other glyph methods, multi_polygons()
accepts a triple-nested
lists of x
and y
positions for the exterior and holes composing
each polygon. The multi_polygons()
method also expects a scalar value or a
list of scalars for each item for parameters such as color, alpha, and line
width. You can similarly use a ColumnDataSource
consisting of a triple-
nested list of point coordinates and a list of scalars, with the top-level
list of point coordinates being of equal length with the list of scalars.
Simple polygon#
The following example generates a single polygon from a triple-nested list of
one-dimensional sequences of x
and y
points using the multi_polygons()
glyph method.
from bokeh.plotting import figure, output_file, show
output_file('multipolygon_simple.html')
p = figure(width=400, height=400)
p.multi_polygons(xs=[[[[1, 1, 2, 2]]]],
ys=[[[[3, 4, 4, 3]]]])
show(p)
Polygon with holes#
The following example generates a single polygon with holes from three
sequences of x
and y
points. The first sequence represents
the exterior of the polygon and the following sequences represent the holes.
from bokeh.plotting import figure, output_file, show
output_file('multipolygon_with_holes.html')
p = figure(width=400, height=400)
p.multi_polygons(xs=[[[ [1, 2, 2, 1], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ]]],
ys=[[[ [3, 3, 4, 4], [3.2, 3.6, 3.2], [3.4, 3.8, 3.8] ]]])
show(p)
Multi-polygon with separate parts#
A single polygon concept can comprise multiple polygon geometries. The
following example generates a multi-polygon glyph from several sequences of
x
and y
points. Each item in the sequence represents a part of the
glyph.
from bokeh.plotting import figure, output_file, show
output_file('multipolygon_with_separate_parts.html')
p = figure(width=400, height=400)
p.multi_polygons(xs=[[[ [1, 1, 2, 2], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ], [ [3, 4, 3] ]]],
ys=[[[ [4, 3, 3, 4], [3.2, 3.2, 3.6], [3.4, 3.8, 3.8] ], [ [1, 1, 3] ]]])
show(p)
Multiple multi-polygons#
The top-level of nesting separates each multi-polygon from the rest. You can think of each multi-polygon as a row in the data source, potentially with a corresponding label or color.
from bokeh.plotting import figure, output_file, show
output_file('multipolygons.html')
p = figure(width=400, height=400)
p.multi_polygons(
xs=[
[[ [1, 1, 2, 2], [1.2, 1.6, 1.6], [1.8, 1.8, 1.6] ], [ [3, 3, 4] ]],
[[ [1, 2, 2, 1], [1.3, 1.3, 1.7, 1.7] ]]],
ys=[
[[ [4, 3, 3, 4], [3.2, 3.2, 3.6], [3.4, 3.8, 3.8] ], [ [1, 3, 1] ]],
[[ [1, 1, 2, 2], [1.3, 1.7, 1.7, 1.3] ]]],
color=['blue', 'red'])
show(p)
Ellipses#
The ellipse()
glyph method accepts the same properties as rect()
, but renders
ellipse shapes.
from math import pi
from bokeh.plotting import figure, output_file, show
output_file('ellipses.html')
p = figure(width=400, height=400)
p.ellipse(x=[1, 2, 3], y=[1, 2, 3], width=[0.2, 0.3, 0.1], height=0.3,
angle=pi/3, color="#CAB2D6")
show(p)
Images#
You can display images on Bokeh plots using the image()
, image_rgba()
, and
image_url()
glyph methods. You can use hovering tooltips with image glyphs
to let the user see the values of each pixel. For more information on how to
enable hovering tooltips for images, see
Image hover.
Raw RGBA data#
The following example shows how to display images using raw RGBA data with the
image_rgba()
method.
import numpy as np
from bokeh.plotting import figure, output_file, show
# create an array of RGBA data
N = 20
img = np.empty((N, N), dtype=np.uint32)
view = img.view(dtype=np.uint8).reshape((N, N, 4))
for i in range(N):
for j in range(N):
view[i, j, 0] = int(255 * i / N)
view[i, j, 1] = 158
view[i, j, 2] = int(255 * j / N)
view[i, j, 3] = 255
output_file("image_rgba.html")
p = figure(width=400, height=400, x_range=(0, 10), y_range=(0, 10))
p.image_rgba(image=[img], x=[0], y=[0], dw=[10], dh=[10])
show(p)
Color mapped images#
The following example shows how to supply an array of scalar values and have
Bokeh automatically color map the data in the browser with the image()
glyph
method.
import numpy as np
from bokeh.plotting import figure, output_file, show
output_file("image.html", title="image.py example")
x = np.linspace(0, 10, 250)
y = np.linspace(0, 10, 250)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx)*np.cos(yy)
p = figure(width=400, height=400)
p.x_range.range_padding = p.y_range.range_padding = 0
p.image(image=[d], x=0, y=0, dw=10, dh=10, palette="Sunset11", level="image")
p.grid.grid_line_width = 0.5
show(p)
Note that this example sets the render level to "image"
. Normally, Bokeh
draws all glyphs above grid lines, but with this render level they appear
below the grid lines.
Origin and Anchor#
The image()
and image_rgba()
glyphs provide origin
and anchor
properties for controlling the relative position and orientation of the
image.
When drawn, the image will cover a rectangular drawing region of size
dw
by dh
.
The anchor
property specifies where that rectangular drawing region
is located, relative to the glyph coordinates x
and y
. It can be
used to shift the image vertically or horizontally from x
and y
.
The origin
property specifies which corner of the rectangular drawing
region corresponds to the [0, 0]
pixel of the image array. It can be
used to flip the image vertically or horizontally within its drawing region.
The example below lets you explore all the different combinations of
anchor
an origin
for a simple 2x2 image.
Segments and rays#
To draw multiple individual line segments use the segment()
and ray()
glyph
methods.
The segment()
method accepts the starting points x0
and y0
and end
points x1
and y1
. It renders segments between those points.
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.segment(x0=[1, 2, 3], y0=[1, 2, 3], x1=[1.2, 2.4, 3.1],
y1=[1.2, 2.5, 3.7], color="#F4A582", line_width=3)
show(p)
The ray()
method accepts the starting points x
and y
with a length
(in screen units) and an angle
. The angle_units
parameter defaults to
"rad"
but you can also set it to "deg"
to have the angle measured in
degrees instead of radians. To have an “infinite” ray that always extends to the
edge of the plot, set length
to 0
.
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.ray(x=[1, 2, 3], y=[1, 2, 3], length=45, angle=[30, 45, 60],
angle_units="deg", color="#FB8072", line_width=2)
show(p)
Wedges and arcs#
To draw a simple line arc, use the arc()
glyph method, which accepts
radius
, start_angle
, and end_angle
to determine position.
Additionally, the direction
property determines whether to render
clockwise ("clock"
) or anti-clockwise ("anticlock"
) between the start
and end angles.
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.arc(x=[1, 2, 3], y=[1, 2, 3], radius=0.1, start_angle=0.4, end_angle=4.8, color="navy")
show(p)
The wedge()
glyph method accepts the same properties as arc()
but renders a
filled wedge instead:
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.wedge(x=[1, 2, 3], y=[1, 2, 3], radius=0.2, start_angle=0.4, end_angle=4.8,
color="firebrick", alpha=0.6, direction="clock")
show(p)
The annular_wedge()
glyph method is similar to wedge()
but leaves an inner
portion of the wedge hollow. It accepts an inner_radius
and
outer_radius
instead of just radius
.
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.annular_wedge(x=[1, 2, 3], y=[1, 2, 3], inner_radius=0.1, outer_radius=0.25,
start_angle=0.4, end_angle=4.8, color="green", alpha=0.6)
show(p)
Finally, the annulus()
glyph method also accepts inner_radius
and
outer_radius
to produce hollow circles.
from bokeh.plotting import figure, show
p = figure(width=400, height=400)
p.annulus(x=[1, 2, 3], y=[1, 2, 3], inner_radius=0.1, outer_radius=0.25,
color="orange", alpha=0.6)
show(p)
Specialized curves#
To draw parameterized quadratic and cubic curves, use the quadratic()
and
bezier()
glyph methods. For more detail on these curves, see
reference documentation.
Combining multiple glyphs#
You can combine multiple glyphs on a single plot by calling their methods on a
single figure()
.
from bokeh.plotting import figure, output_file, show
x = [1, 2, 3, 4, 5]
y = [6, 7, 8, 7, 3]
output_file("multiple.html")
p = figure(width=400, height=400)
# add both a line and circles on the same plot
p.line(x, y, line_width=2)
p.circle(x, y, fill_color="white", size=8)
show(p)
This principle applies to all bokeh.plotting glyph methods. You can add as many glyphs to a Bokeh plot as you want.
Setting ranges#
By default, Bokeh attempts to automatically set the data bounds of plots to fit
snugly around the data. You may, however, need to set a plot’s range
explicitly. To do so, set the x_range
and/or y_range
properties using a
Range1d
object that lets you set the start and end points of the range
you want.
p.x_range = Range1d(0, 100)
For convenience, the figure()
function can also accept (start, end) tuples as
values for the x_range
or y_range
parameters. Here’s how you can use
both methods to set a range:
from bokeh.models import Range1d
from bokeh.plotting import figure, output_file, show
output_file("title.html")
# create a new plot with a range set with a tuple
p = figure(width=400, height=400, x_range=(0, 20))
# set a range using a Range1d
p.y_range = Range1d(0, 15)
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=10)
show(p)
Ranges also have a bounds
property that lets you specify the limits of the
plot beyond which the user cannot pan or zoom.
# set a range using a Range1d
p.y_range = Range1d(0, 15, bounds=(0, None))
Specifying axis types#
All the examples above use the default linear axis. This axis is suitable for plots that need to show numerical data on a linear scale. However, you may have categorical data or need to display numerical data on a datetime or log scale. This section shows you how to specify the axis type when using the bokeh.plotting interface.
Categorical axes#
To create a categorical axis, specify a
FactorRange
for one of the plot’s ranges or a
list of factors to be converted to one. Here’s an example:
from bokeh.plotting import figure, output_file, show
factors = ["a", "b", "c", "d", "e", "f", "g", "h"]
x = [50, 40, 65, 10, 25, 37, 80, 60]
output_file("categorical.html")
p = figure(y_range=factors)
p.circle(x, factors, size=15, fill_color="orange", line_color="green", line_width=3)
show(p)
For complete details, see Handling categorical data.
Datetime axes#
Note
The example in this section requires a network connection and depends on the open source Pandas library to present realistic time series data.
For time series, or any data that involves dates or time, you may want to use axes with labels suitable for different date and time scales.
The figure()
function accepts x_axis_type
and y_axis_type
as arguments.
To specify a datetime axis, pass "datetime"
for the value of either of
these parameters.
import pandas as pd
from bokeh.plotting import figure, output_file, show
from bokeh.sampledata.stocks import AAPL
df = pd.DataFrame(AAPL)
df['date'] = pd.to_datetime(df['date'])
output_file("datetime.html")
# create a new plot with a datetime axis type
p = figure(width=800, height=250, x_axis_type="datetime")
p.line(df['date'], df['close'], color='navy', alpha=0.5)
show(p)
Note
Future versions of Bokeh will attempt to auto-detect situations when datetime axes are appropriate and add them automatically.
Log scale axes#
Data that grows exponentially or covers many orders of magnitude often requires one axis to be on a log scale. For data that has a power law relationship, you may want to use log scales on both axes.
You can use the same figure()
arguments, x_axis_type
and y_axis_type
,
to set one or both of the axes to "log"
.
By default, Bokeh calculates log axis ranges to fit around positive value data. For information on how to set your own ranges, see Setting ranges.
from bokeh.plotting import figure, output_file, show
x = [0.1, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
y = [10**xx for xx in x]
output_file("log.html")
# create a new plot with a log axis type
p = figure(width=400, height=400, y_axis_type="log")
p.line(x, y, line_width=2)
p.circle(x, y, fill_color="white", size=8)
show(p)
Twin axes#
You can add multiple axes representing different ranges to a single plot. To do
this, configure the plot with “extra” named ranges in the extra_x_range
and
extra_y_range
properties. You can then refer to these named ranges when
adding new glyph methods as well as when adding new axis objects with the
add_layout
method of the Plot
. Here’s an example:
from numpy import arange, linspace, pi, sin
from bokeh.models import LinearAxis, Range1d
from bokeh.plotting import figure, output_file, show
x = arange(-2*pi, 2*pi, 0.1)
y = sin(x)
y2 = linspace(0, 100, len(y))
output_file("twin_axis.html")
p = figure(x_range=(-6.5, 6.5), y_range=(-1.1, 1.1))
p.circle(x, y, color="red")
p.extra_y_ranges = {"foo": Range1d(start=0, end=100)}
p.circle(x, y2, color="blue", y_range_name="foo")
p.add_layout(LinearAxis(y_range_name="foo"), 'left')
show(p)