Last active
August 22, 2021 23:20
-
-
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!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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">¶</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">¶</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 %} | |
<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 %}</code></pre> | |
<p>It embeds code cells in simple <code><pre></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><head></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">¶</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">¶</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">¶</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">¶</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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{%- 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 %} |
never mind, copy/paste error in thebe.tpl
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
not working yet for me. Not sure why.
I'm running
jupyter-nbconvert
instead ofnbconvert
, 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.