Skip to content

Instantly share code, notes, and snippets.

@betatim
Last active August 22, 2021 23:20
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save betatim/349b82147b4860339925 to your computer and use it in GitHub Desktop.
Save betatim/349b82147b4860339925 to your computer and use it in GitHub Desktop.
🚀 Minimal `nbconvert` template to convert jupyter notebooks to HTML + thebe (details: https://betatim.github.io/posts/really-interactive-posts/). Open the generated HTML in a browser it will look like a normal notebook except for the code cells, which you can edit and execute!
# Convert your notebook to an interactive webpage
#
# Attached a notebook (really-interactive-posts.ipynb) and the generated
# output (really-interactive-posts.html). The thebe.tpl template file is
# at the very end of the gist.
$ jupyter nbconvert --template thebe.tpl --to html <notebook.ipynb>
# You can open the generated webpage locally file://... howerver some
# resources will not load properly. Best to open it from a webserver:
$ python3 -m http.server
# or if you'd rather have a local https server (credit to: https://gist.github.com/dergachev/7028596)
# generate certificate with the following command:
$ openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
$ echo "import BaseHTTPServer, SimpleHTTPServer
import ssl
httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True)
httpd.serve_forever()" > simple-https-server.py
# run with:
$ python simple-https-server.py
# In your browser, visit:
# https://localhost:4443
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.10.0/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/oreillymedia/thebe/17fe0971303cac24d7e806c8f1bc8ba3c0c40b23/static/main-built.js"></script>
<script>
$(function(){
new Thebe({url:"https://tmpnb.org/",
kernel_name: "python3"});
});
</script>
</head>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h1 id="Really-Interactive-Blog-Posts">Really Interactive Blog Posts<a class="anchor-link" href="#Really-Interactive-Blog-Posts">&#182;</a></h1><p><em>This notebook first appeared as a <a href="//betatim.github.io/posts/really-interactive-blog-posts">blog post</a> on <a href="//betatim.github.io">Tim Head</a>'s blog.</em></p>
<p><em>License: <a href="http://opensource.org/licenses/MIT">MIT</a></em></p>
<p><em>(C) 2016, Tim Head.</em>
<em>Feel free to use, distribute, and modify with the above attribution.</em></p>
</div>
</div>
</div>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>A few days ago I started making my <a href="/posts/interactive-posts/">blog posts interactive</a>.
It was cool, but required you to surf to a different page for the interactive
experience, while the original post was still non-interactive.</p>
<p><a href="//alexpearce.me">Alex</a> pointed out that really you wanted it all in one
page. Basically he was:</p>
<p><img src="/images/meh.gif" width="480" height="270" /></p>
<p>My blog setup follows <a href="https://jakevdp.github.io/blog/2013/05/07/migrating-from-octopress-to-pelican/">Jake Vanderplas'</a> pretty closely. So I created a new <a href="https://github.com/getpelican/pelican-plugins/tree/master/liquid_tags"><code>liquid_tags</code></a> plugin
that has its own template for <code>nbconvert</code> which generates HTML that <a href="https://github.com/oreillymedia/thebe">thebe</a> understands.</p>
<p>No downloading, no installing, no browsing to a separate page! Just interactive blog
posts! (Scroll down to see it in action if you do not care how it was done.)</p>
<h2 id="Do-it-yourself">Do it yourself<a class="anchor-link" href="#Do-it-yourself">&#182;</a></h2><p>I am preparing a write up of how to modify your own <a href="http://blog.getpelican.com/">pelican</a> site have an interactive section. For the keen and eager people, the most important part is using the following template with <code>nbconvert</code>:</p>
<pre><code>{%- extends 'basic.tpl' -%}
{% block codecell %}
&lt;pre data-executable&gt;
{{ cell.source }}
&lt;/pre&gt;
{% endblock codecell %}
{% block markdowncell scoped %}
&lt;div class="cellOOO border-box-sizing text_cell rendered"&gt;
{{ self.empty_in_prompt() }}
&lt;div class="inner_cell"&gt;
&lt;div class="text_cell_render border-box-sizing rendered_html"&gt;
{{ cell.source | markdown2html | strip_files_prefix }}
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
{%- endblock markdowncell %}</code></pre>
<p>It embeds code cells in simple <code>&lt;pre&gt;</code> tags and modifies non-code cells so that they do not
match the selectors used inside the notebook machinery. That is it. Then stick a bit of CSS and JS in the <code>&lt;head&gt;</code> of your web page and you are good to go (use the source of this one for inspiration).</p>
<h3 id="Credits">Credits<a class="anchor-link" href="#Credits">&#182;</a></h3><p>Compared to my previous post this setup now only relies on <a href="https://github.com/oreillymedia/thebe">thebe</a>, <a href="https://tmpnb.org">tmpnb</a>, and the kind people at <a href="https://developer.rackspace.com/">rackspace</a> who sponsor the computing power for <code>tmpnb</code>.</p>
<p>Below, the work of the jupyter development team, licensed under the 3 clause BSD license.</p>
<p>Get in touch on twitter @<a href="//twitter.com/betatim">betatim</a>.</p>
</div>
</div>
</div>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h1 id="Exploring-the-Lorenz-System-of-Differential-Equations">Exploring the Lorenz System of Differential Equations<a class="anchor-link" href="#Exploring-the-Lorenz-System-of-Differential-Equations">&#182;</a></h1>
</div>
</div>
</div>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>In this Notebook we explore the Lorenz system of differential equations:</p>
$$
\begin{aligned}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{aligned}
$$<p>This is one of the classic systems in non-linear differential equations. It exhibits a range of different behaviors as the parameters ($\sigma$, $\beta$, $\rho$) are varied.</p>
</div>
</div>
</div>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h2 id="Imports">Imports<a class="anchor-link" href="#Imports">&#182;</a></h2>
</div>
</div>
</div>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>First, we import the needed things from IPython, NumPy, Matplotlib and SciPy.</p>
</div>
</div>
</div>
<pre data-executable>
%matplotlib inline
</pre>
<pre data-executable>
from ipywidgets import interact, interactive
from IPython.display import clear_output, display, HTML
</pre>
<pre data-executable>
import numpy as np
from scipy import integrate
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.colors import cnames
from matplotlib import animation
</pre>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<h2 id="Computing-the-trajectories-and-plotting-the-result">Computing the trajectories and plotting the result<a class="anchor-link" href="#Computing-the-trajectories-and-plotting-the-result">&#182;</a></h2>
</div>
</div>
</div>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>We define a function that can integrate the differential equations numerically and then plot the solutions. This function has arguments that control the parameters of the differential equation ($\sigma$, $\beta$, $\rho$), the numerical integration (<code>N</code>, <code>max_time</code>) and the visualization (<code>angle</code>).</p>
</div>
</div>
</div>
<pre data-executable>
def solve_lorenz(N=10, angle=0.0, max_time=4.0, sigma=10.0, beta=8./3, rho=28.0):
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.axis('off')
# prepare the axes limits
ax.set_xlim((-25, 25))
ax.set_ylim((-35, 35))
ax.set_zlim((5, 55))
def lorenz_deriv(x_y_z, t0, sigma=sigma, beta=beta, rho=rho):
"""Compute the time-derivative of a Lorenz system."""
x, y, z = x_y_z
return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z]
# Choose random starting points, uniformly distributed from -15 to 15
np.random.seed(1)
x0 = -15 + 30 * np.random.random((N, 3))
# Solve for the trajectories
t = np.linspace(0, max_time, int(250*max_time))
x_t = np.asarray([integrate.odeint(lorenz_deriv, x0i, t)
for x0i in x0])
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, N))
for i in range(N):
x, y, z = x_t[i,:,:].T
lines = ax.plot(x, y, z, '-', c=colors[i])
plt.setp(lines, linewidth=2)
ax.view_init(30, angle)
plt.show()
return t, x_t
</pre>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>Let's call the function once to view the solutions. For this set of parameters, we see the trajectories swirling around two points, called attractors.</p>
</div>
</div>
</div>
<pre data-executable>
t, x_t = solve_lorenz(angle=0, N=10)
</pre>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>Using IPython's <code>interactive</code> function, we can explore how the trajectories behave as we change the various parameters.</p>
</div>
</div>
</div>
<pre data-executable>
w = interactive(solve_lorenz, angle=(0.,360.), N=(0,50), sigma=(0.0,50.0), rho=(0.0,50.0))
display(w)
</pre>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>The object returned by <code>interactive</code> is a <code>Widget</code> object and it has attributes that contain the current result and arguments:</p>
</div>
</div>
</div>
<pre data-executable>
t, x_t = w.result
</pre>
<pre data-executable>
w.kwargs
</pre>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>After interacting with the system, we can take the result and perform further computations. In this case, we compute the average positions in $x$, $y$ and $z$.</p>
</div>
</div>
</div>
<pre data-executable>
xyz_avg = x_t.mean(axis=1)
</pre>
<pre data-executable>
xyz_avg.shape
</pre>
<div class="cellOOO border-box-sizing text_cell rendered">
<div class="prompt input_prompt">
</div>
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
<p>Creating histograms of the average positions (across different trajectories) show that on average the trajectories swirl about the attractors.</p>
</div>
</div>
</div>
<pre data-executable>
plt.hist(xyz_avg[:,0])
plt.title('Average $x(t)$')
</pre>
<pre data-executable>
plt.hist(xyz_avg[:,1])
plt.title('Average $y(t)$')
</pre>
<pre data-executable>
</pre>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
{%- extends 'basic.tpl' -%}
{% block header %}
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.10.0/codemirror.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript" src="https://cdn.rawgit.com/oreillymedia/thebe/17fe0971303cac24d7e806c8f1bc8ba3c0c40b23/static/main-bu\
ilt.js"></script>
<script>
$(function(){
new Thebe({url:"https://tmpnb.org/",
kernel_name: "python3"});
});
</script>
</head>
{% endblock header %}
{% block codecell %}
<pre data-executable>
{{ cell.source }}
</pre>
{% endblock codecell %}
{% block markdowncell scoped %}
<div class="cellOOO border-box-sizing text_cell rendered">
{{ self.empty_in_prompt() }}
<div class="inner_cell">
<div class="text_cell_render border-box-sizing rendered_html">
{{ cell.source | markdown2html | strip_files_prefix }}
</div>
</div>
</div>
{%- endblock markdowncell %}
@cranmer
Copy link

cranmer commented Jan 11, 2016

not working yet for me. Not sure why.
I'm running jupyter-nbconvert instead of nbconvert, I don't know if that is of any importance.
I'm putting thebe.tpl and the notebook in a temporary directory.
I don't know if thebe needs to be installed for this to work (since html points to a cdn) but I did install it.
It generates html that seems reasonable, but when I point my web server at it there are no "run" buttons.
I could be missing something very obvious.

@cranmer
Copy link

cranmer commented Jan 11, 2016

never mind, copy/paste error in thebe.tpl

@mlgill
Copy link

mlgill commented Feb 3, 2016

I have this working great and am now trying to incorporate your liquid tags branch into my blog.

When I run pelican, I get the following error:

ERROR: Cannot load plugin `liquid_tags.thebeliquid_tags.literal`
  | ImportError: No module named thebeliquid_tags.literal

I've read through your only commit to the appropriate branch of your fork of pelican plugins and I'm not sure where this error is coming from.

I've tried with both python 3.4 and 2.7 and get the same error. I'm also using version 4.1.0 of Jupyter notebook. I realize things may not work with CSS classes yet (I'm trying to help fix that if it's an issue), but I wouldn't have thought this would produce an import error.

Any thoughts? Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment