Bokeh is a newer method of plotting in Jupyter notebooks, and one I'm honestly still learning. However, the resulting output is nicer than Matplotlibs results and the syntax arguably either, so it might be worth your time to learn. You can of course use whichever you like more for all your plotting purposes. One thing I will warn you about: Bokeh does not yet have a simple method for plotting polar graphs!
We begin by importing the needed libraries. output_notebook
is what lets us embed the plots directly in the Jupyter notebook, and figure
and show
are the important plotting functions. I also import numpy
just for dealing with vectors.
from bokeh.io import output_notebook
from bokeh.resources import INLINE #Loading will hang without this on some systems
from bokeh.plotting import figure, show
output_notebook(INLINE)
import numpy as np
For a basic example let us consider two functions: A and B. \begin{align} A(x) &= 2x^3 \\ B(x) &= \frac{x^2}{2} \end{align}
We'll plot them over the interval from -1 to 1 with 50 data points.
def A(x):
return 2*x**3
def B(x):
return x**2/2
xs = np.linspace(-1,1,50)
fig1 = figure()
fig1.line(xs, A(xs))
fig1.square(xs, B(xs))
show(fig1)
This is the basic output from a Bokeh plot. Note that you have some interactivity with the plot. You can pan around by dragging and check out the buttons on the right for zooming and selection capabilities. We can vary up the plotting options in a straightforward way:
fig2 = figure()
fig2.circle(xs, A(xs), color='orange', size=20, alpha=0.5)
fig2.triangle(xs, B(xs), color='purple', size=10, alpha=0.5)
show(fig2)
All good plots should label the axes and plot though! In Bokeh you can either label the axes when you create the figure itself or after the fact. Here I'll define the x-label and title when I create the figure and the y-label afterwards. I'll also include a legend entry for both plots.
fig3 = figure( x_axis_label='x', title='This here is a plot!' )
fig3.line(xs, A(xs), color='red', line_width=4, line_dash='dashed', legend='A(x)')
fig3.asterisk(xs, B(xs), color='green', size=10, alpha=0.5, legend=('B(x)'))
fig3.yaxis.axis_label='y'
fig3.title.align='center'
show(fig3)
A few other options include eliminating the side toolbar if you don't like it or changing the dimensions of the plot:
fig4 = figure( width=800, height=400, x_axis_label='x', y_axis_label='y', toolbar_location=None )
fig4.cross(xs, A(xs), color='blue', size=10, legend='A(x)')
fig4.line(xs, B(xs))
fig4.diamond(xs, B(xs), fill_color='white', size=10, legend=('B(x)'))
show(fig4)
And finally, you can easily group together plots into joint displays.
from bokeh.layouts import gridplot, row, column
show(gridplot([fig1,fig2,fig3], ncols=2, plot_width=400, plot_height=400))
Bokeh by itself doesn't really have good support for heat maps and contour plots. There does exist a library called holoviews
though that builds on top of Bokeh and can create these sorts of plots. Just realize you in may not come installed by default. But you are welcome to use it!
To import, just import holoviews as hv
and then tell it you want it to be used with 'bokeh':
import holoviews as hv
from holoviews import opts
hv.extension('bokeh')
You should get the little bokeh symbol along with the holoviews emblem if all went well. Then we can proceed to start plotting things. Some of these details are the exact same as in Matplotlib, so see that guide if something confuses you.
Lets take the case where we want to plot $$U(x,y) = (1-(x^2+y^3)e^{-(x^2+y^2)/2)}$$ We'll go ahead and define our function and the interval we want to plot it over just like in Matplotlib.
def U(x,y):
return (1-(x**2+y**3))*np.exp(-(x**2+y**2)/2)
xs = np.arange(-3,3, 0.01) # Measurement points in X
ys = np.arange(-3,3, 0.01) # Measurement points in Y
Xgrid, Ygrid = np.meshgrid( xs, ys) # Generating the grid
Now, to create a heatmap of the situation we'll use Holoviews Image
function:
img = hv.Image(
U(Xgrid,Ygrid),
bounds = (-3,-3,3,3),
)
img.opts(width=600,
height=500,
cmap='magma',
colorbar = True,
default_tools = ['wheel_zoom', 'reset'],
active_tools = ['wheel_zoom']
)
There is still something of a problem here in that the image is inverted and the axis labels don't indicate that. I'm trying to figure out how to fix that.
Should we want to make a contour plot, we can use the following, where first we calculate the location of the contours and then style them.
conts = hv.operation.contours(img, levels=10)
conts.opts(
opts.Contours(
cmap='magma',
colorbar=True,
default_tools=['hover', 'wheel_zoom', 'reset'],
width=600,
height=500
)
)
We also have those instances where we'd like to plot a vector field. Here we'll take the same field we looked at in class $$ \vec{F}(\vec{r}) = 3y\hat{x} - x\hat{y}$$ and we'd like to look at it in the first quadrant (also as we did in class):
def F(r):
x = r[0]
y = r[1]
return 3*y, -x
xs, ys = np.linspace(0.1,5,15), np.linspace(0.1,5,15)
X,Y = np.meshgrid(xs,ys)
Fx,Fy = F((X,Y))
Interestingly, Holoviews is incapable of figuring out the magnitudes and angles of the vectors on its own, so we need to do that for it:
mag = np.hypot(Fx,Fy)
ang = np.pi/2 - np.arctan2(Fx/mag,Fy/mag)
Seems we divided by zero at one point, but not a huge deal. Now we can visualize things:
opts.defaults(opts.VectorField(height=400, width=400, padding=0.1))
hv.VectorField((xs, ys, ang, mag))