XKCD_plots.orig.tex
626 lines
| 19.9 KiB
| application/x-tex
|
TexLexer
Matthias BUSSONNIER
|
r9595 | %% This file was auto-generated by IPython. | ||
%% Conversion from the original notebook file: | ||||
%% tests/ipynbref/XKCD_plots.orig.ipynb | ||||
%% | ||||
\documentclass[11pt,english]{article} | ||||
%% This is the automatic preamble used by IPython. Note that it does *not* | ||||
%% include a documentclass declaration, that is added at runtime to the overall | ||||
%% document. | ||||
\usepackage{amsmath} | ||||
\usepackage{amssymb} | ||||
\usepackage{graphicx} | ||||
\usepackage{ucs} | ||||
\usepackage[utf8x]{inputenc} | ||||
% needed for markdown enumerations to work | ||||
\usepackage{enumerate} | ||||
% Slightly bigger margins than the latex defaults | ||||
\usepackage{geometry} | ||||
\geometry{verbose,tmargin=3cm,bmargin=3cm,lmargin=2.5cm,rmargin=2.5cm} | ||||
% Define a few colors for use in code, links and cell shading | ||||
\usepackage{color} | ||||
\definecolor{orange}{cmyk}{0,0.4,0.8,0.2} | ||||
\definecolor{darkorange}{rgb}{.71,0.21,0.01} | ||||
\definecolor{darkgreen}{rgb}{.12,.54,.11} | ||||
\definecolor{myteal}{rgb}{.26, .44, .56} | ||||
\definecolor{gray}{gray}{0.45} | ||||
\definecolor{lightgray}{gray}{.95} | ||||
\definecolor{mediumgray}{gray}{.8} | ||||
\definecolor{inputbackground}{rgb}{.95, .95, .85} | ||||
\definecolor{outputbackground}{rgb}{.95, .95, .95} | ||||
\definecolor{traceback}{rgb}{1, .95, .95} | ||||
% Framed environments for code cells (inputs, outputs, errors, ...). The | ||||
% various uses of \unskip (or not) at the end were fine-tuned by hand, so don't | ||||
% randomly change them unless you're sure of the effect it will have. | ||||
\usepackage{framed} | ||||
% remove extraneous vertical space in boxes | ||||
\setlength\fboxsep{0pt} | ||||
% codecell is the whole input+output set of blocks that a Code cell can | ||||
% generate. | ||||
% TODO: unfortunately, it seems that using a framed codecell environment breaks | ||||
% the ability of the frames inside of it to be broken across pages. This | ||||
% causes at least the problem of having lots of empty space at the bottom of | ||||
% pages as new frames are moved to the next page, and if a single frame is too | ||||
% long to fit on a page, will completely stop latex from compiling the | ||||
% document. So unless we figure out a solution to this, we'll have to instead | ||||
% leave the codecell env. as empty. I'm keeping the original codecell | ||||
% definition here (a thin vertical bar) for reference, in case we find a | ||||
% solution to the page break issue. | ||||
%% \newenvironment{codecell}{% | ||||
%% \def\FrameCommand{\color{mediumgray} \vrule width 1pt \hspace{5pt}}% | ||||
%% \MakeFramed{\vspace{-0.5em}}} | ||||
%% {\unskip\endMakeFramed} | ||||
% For now, make this a no-op... | ||||
\newenvironment{codecell}{} | ||||
\newenvironment{codeinput}{% | ||||
\def\FrameCommand{\colorbox{inputbackground}}% | ||||
\MakeFramed{\advance\hsize-\width \FrameRestore}} | ||||
{\unskip\endMakeFramed} | ||||
\newenvironment{codeoutput}{% | ||||
\def\FrameCommand{\colorbox{outputbackground}}% | ||||
\vspace{-1.4em} | ||||
\MakeFramed{\advance\hsize-\width \FrameRestore}} | ||||
{\unskip\medskip\endMakeFramed} | ||||
\newenvironment{traceback}{% | ||||
\def\FrameCommand{\colorbox{traceback}}% | ||||
\MakeFramed{\advance\hsize-\width \FrameRestore}} | ||||
{\endMakeFramed} | ||||
% Use and configure listings package for nicely formatted code | ||||
\usepackage{listingsutf8} | ||||
\lstset{ | ||||
language=python, | ||||
inputencoding=utf8x, | ||||
extendedchars=\true, | ||||
aboveskip=\smallskipamount, | ||||
belowskip=\smallskipamount, | ||||
xleftmargin=2mm, | ||||
breaklines=true, | ||||
basicstyle=\small \ttfamily, | ||||
showstringspaces=false, | ||||
keywordstyle=\color{blue}\bfseries, | ||||
commentstyle=\color{myteal}, | ||||
stringstyle=\color{darkgreen}, | ||||
identifierstyle=\color{darkorange}, | ||||
columns=fullflexible, % tighter character kerning, like verb | ||||
} | ||||
% The hyperref package gives us a pdf with properly built | ||||
% internal navigation ('pdf bookmarks' for the table of contents, | ||||
% internal cross-reference links, web links for URLs, etc.) | ||||
\usepackage{hyperref} | ||||
\hypersetup{ | ||||
breaklinks=true, % so long urls are correctly broken across lines | ||||
colorlinks=true, | ||||
urlcolor=blue, | ||||
linkcolor=darkorange, | ||||
citecolor=darkgreen, | ||||
} | ||||
% hardcode size of all verbatim environments to be a bit smaller | ||||
\makeatletter | ||||
\g@addto@macro\@verbatim\small\topsep=0.5em\partopsep=0pt | ||||
\makeatother | ||||
% Prevent overflowing lines due to urls and other hard-to-break entities. | ||||
\sloppy | ||||
\begin{document} | ||||
\section{XKCD plots in Matplotlib} | ||||
This notebook originally appeared as a blog post at | ||||
\href{http://jakevdp.github.com/blog/2012/10/07/xkcd-style-plots-in-matplotlib/}{Pythonic | ||||
Perambulations} by Jake Vanderplas. | ||||
One of the problems I've had with typical matplotlib figures is that | ||||
everything in them is so precise, so perfect. For an example of what I | ||||
mean, take a look at this figure: | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
from IPython.display import Image | ||||
Image('http://jakevdp.github.com/figures/xkcd_version.png') | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\begin{codeoutput} | ||||
\begin{verbatim} | ||||
<IPython.core.display.Image at 0x2fef710> | ||||
\end{verbatim} | ||||
\end{codeoutput} | ||||
\end{codecell} | ||||
Sometimes when showing schematic plots, this is the type of figure I | ||||
want to display. But drawing it by hand is a pain: I'd rather just use | ||||
matplotlib. The problem is, matplotlib is a bit too precise. Attempting | ||||
to duplicate this figure in matplotlib leads to something like this: | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
Image('http://jakevdp.github.com/figures/mpl_version.png') | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\begin{codeoutput} | ||||
\begin{verbatim} | ||||
<IPython.core.display.Image at 0x2fef0d0> | ||||
\end{verbatim} | ||||
\end{codeoutput} | ||||
\end{codecell} | ||||
It just doesn't have the same effect. Matplotlib is great for scientific | ||||
plots, but sometimes you don't want to be so precise. | ||||
This subject has recently come up on the matplotlib mailing list, and | ||||
started some interesting discussions. As near as I can tell, this | ||||
started with a thread on a | ||||
\href{http://mathematica.stackexchange.com/questions/11350/xkcd-style-graphs}{mathematica | ||||
list} which prompted a thread on the | ||||
\href{http://matplotlib.1069221.n5.nabble.com/XKCD-style-graphs-td39226.html}{matplotlib | ||||
list} wondering if the same could be done in matplotlib. | ||||
Damon McDougall offered a quick | ||||
\href{http://www.mail-archive.com/matplotlib-users@lists.sourceforge.net/msg25499.html}{solution} | ||||
which was improved by Fernando Perez in | ||||
\href{http://nbviewer.ipython.org/3835181/}{this notebook}, and within a | ||||
few days there was a | ||||
\href{https://github.com/matplotlib/matplotlib/pull/1329}{matplotlib | ||||
pull request} offering a very general way to create sketch-style plots | ||||
in matplotlib. Only a few days from a cool idea to a working | ||||
implementation: this is one of the most incredible aspects of package | ||||
development on github. | ||||
The pull request looks really nice, but will likely not be included in a | ||||
released version of matplotlib until at least version 1.3. In the | ||||
mean-time, I wanted a way to play around with these types of plots in a | ||||
way that is compatible with the current release of matplotlib. To do | ||||
that, I created the following code: | ||||
\subsection{The Code: XKCDify} | ||||
XKCDify will take a matplotlib \texttt{Axes} instance, and modify the | ||||
plot elements in-place to make them look hand-drawn. First off, we'll | ||||
need to make sure we have the Humor Sans font. It can be downloaded | ||||
using the command below. | ||||
Next we'll create a function \texttt{xkcd\_line} to add jitter to lines. | ||||
We want this to be very general, so we'll normalize the size of the | ||||
lines, and use a low-pass filter to add correlated noise, perpendicular | ||||
to the direction of the line. There are a few parameters for this filter | ||||
that can be tweaked to customize the appearance of the jitter. | ||||
Finally, we'll create a function which accepts a matplotlib axis, and | ||||
calls \texttt{xkcd\_line} on all lines in the axis. Additionally, we'll | ||||
switch the font of all text in the axes, and add some background lines | ||||
for a nice effect where lines cross. We'll also draw axes, and move the | ||||
axes labels and titles to the appropriate location. | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
""" | ||||
XKCD plot generator | ||||
------------------- | ||||
Author: Jake Vanderplas | ||||
This is a script that will take any matplotlib line diagram, and convert it | ||||
to an XKCD-style plot. It will work for plots with line & text elements, | ||||
including axes labels and titles (but not axes tick labels). | ||||
The idea for this comes from work by Damon McDougall | ||||
http://www.mail-archive.com/matplotlib-users@lists.sourceforge.net/msg25499.html | ||||
""" | ||||
import numpy as np | ||||
import pylab as pl | ||||
from scipy import interpolate, signal | ||||
import matplotlib.font_manager as fm | ||||
# We need a special font for the code below. It can be downloaded this way: | ||||
import os | ||||
import urllib2 | ||||
if not os.path.exists('Humor-Sans.ttf'): | ||||
fhandle = urllib2.urlopen('http://antiyawn.com/uploads/Humor-Sans.ttf') | ||||
open('Humor-Sans.ttf', 'wb').write(fhandle.read()) | ||||
def xkcd_line(x, y, xlim=None, ylim=None, | ||||
mag=1.0, f1=30, f2=0.05, f3=15): | ||||
""" | ||||
Mimic a hand-drawn line from (x, y) data | ||||
Parameters | ||||
---------- | ||||
x, y : array_like | ||||
arrays to be modified | ||||
xlim, ylim : data range | ||||
the assumed plot range for the modification. If not specified, | ||||
they will be guessed from the data | ||||
mag : float | ||||
magnitude of distortions | ||||
f1, f2, f3 : int, float, int | ||||
filtering parameters. f1 gives the size of the window, f2 gives | ||||
the high-frequency cutoff, f3 gives the size of the filter | ||||
Returns | ||||
------- | ||||
x, y : ndarrays | ||||
The modified lines | ||||
""" | ||||
x = np.asarray(x) | ||||
y = np.asarray(y) | ||||
# get limits for rescaling | ||||
if xlim is None: | ||||
xlim = (x.min(), x.max()) | ||||
if ylim is None: | ||||
ylim = (y.min(), y.max()) | ||||
if xlim[1] == xlim[0]: | ||||
xlim = ylim | ||||
if ylim[1] == ylim[0]: | ||||
ylim = xlim | ||||
# scale the data | ||||
x_scaled = (x - xlim[0]) * 1. / (xlim[1] - xlim[0]) | ||||
y_scaled = (y - ylim[0]) * 1. / (ylim[1] - ylim[0]) | ||||
# compute the total distance along the path | ||||
dx = x_scaled[1:] - x_scaled[:-1] | ||||
dy = y_scaled[1:] - y_scaled[:-1] | ||||
dist_tot = np.sum(np.sqrt(dx * dx + dy * dy)) | ||||
# number of interpolated points is proportional to the distance | ||||
Nu = int(200 * dist_tot) | ||||
u = np.arange(-1, Nu + 1) * 1. / (Nu - 1) | ||||
# interpolate curve at sampled points | ||||
k = min(3, len(x) - 1) | ||||
res = interpolate.splprep([x_scaled, y_scaled], s=0, k=k) | ||||
x_int, y_int = interpolate.splev(u, res[0]) | ||||
# we'll perturb perpendicular to the drawn line | ||||
dx = x_int[2:] - x_int[:-2] | ||||
dy = y_int[2:] - y_int[:-2] | ||||
dist = np.sqrt(dx * dx + dy * dy) | ||||
# create a filtered perturbation | ||||
coeffs = mag * np.random.normal(0, 0.01, len(x_int) - 2) | ||||
b = signal.firwin(f1, f2 * dist_tot, window=('kaiser', f3)) | ||||
response = signal.lfilter(b, 1, coeffs) | ||||
x_int[1:-1] += response * dy / dist | ||||
y_int[1:-1] += response * dx / dist | ||||
# un-scale data | ||||
x_int = x_int[1:-1] * (xlim[1] - xlim[0]) + xlim[0] | ||||
y_int = y_int[1:-1] * (ylim[1] - ylim[0]) + ylim[0] | ||||
return x_int, y_int | ||||
def XKCDify(ax, mag=1.0, | ||||
f1=50, f2=0.01, f3=15, | ||||
bgcolor='w', | ||||
xaxis_loc=None, | ||||
yaxis_loc=None, | ||||
xaxis_arrow='+', | ||||
yaxis_arrow='+', | ||||
ax_extend=0.1, | ||||
expand_axes=False): | ||||
"""Make axis look hand-drawn | ||||
This adjusts all lines, text, legends, and axes in the figure to look | ||||
like xkcd plots. Other plot elements are not modified. | ||||
Parameters | ||||
---------- | ||||
ax : Axes instance | ||||
the axes to be modified. | ||||
mag : float | ||||
the magnitude of the distortion | ||||
f1, f2, f3 : int, float, int | ||||
filtering parameters. f1 gives the size of the window, f2 gives | ||||
the high-frequency cutoff, f3 gives the size of the filter | ||||
xaxis_loc, yaxis_log : float | ||||
The locations to draw the x and y axes. If not specified, they | ||||
will be drawn from the bottom left of the plot | ||||
xaxis_arrow, yaxis_arrow : str | ||||
where to draw arrows on the x/y axes. Options are '+', '-', '+-', or '' | ||||
ax_extend : float | ||||
How far (fractionally) to extend the drawn axes beyond the original | ||||
axes limits | ||||
expand_axes : bool | ||||
if True, then expand axes to fill the figure (useful if there is only | ||||
a single axes in the figure) | ||||
""" | ||||
# Get axes aspect | ||||
ext = ax.get_window_extent().extents | ||||
aspect = (ext[3] - ext[1]) / (ext[2] - ext[0]) | ||||
xlim = ax.get_xlim() | ||||
ylim = ax.get_ylim() | ||||
xspan = xlim[1] - xlim[0] | ||||
yspan = ylim[1] - xlim[0] | ||||
xax_lim = (xlim[0] - ax_extend * xspan, | ||||
xlim[1] + ax_extend * xspan) | ||||
yax_lim = (ylim[0] - ax_extend * yspan, | ||||
ylim[1] + ax_extend * yspan) | ||||
if xaxis_loc is None: | ||||
xaxis_loc = ylim[0] | ||||
if yaxis_loc is None: | ||||
yaxis_loc = xlim[0] | ||||
# Draw axes | ||||
xaxis = pl.Line2D([xax_lim[0], xax_lim[1]], [xaxis_loc, xaxis_loc], | ||||
linestyle='-', color='k') | ||||
yaxis = pl.Line2D([yaxis_loc, yaxis_loc], [yax_lim[0], yax_lim[1]], | ||||
linestyle='-', color='k') | ||||
# Label axes3, 0.5, 'hello', fontsize=14) | ||||
ax.text(xax_lim[1], xaxis_loc - 0.02 * yspan, ax.get_xlabel(), | ||||
fontsize=14, ha='right', va='top', rotation=12) | ||||
ax.text(yaxis_loc - 0.02 * xspan, yax_lim[1], ax.get_ylabel(), | ||||
fontsize=14, ha='right', va='top', rotation=78) | ||||
ax.set_xlabel('') | ||||
ax.set_ylabel('') | ||||
# Add title | ||||
ax.text(0.5 * (xax_lim[1] + xax_lim[0]), yax_lim[1], | ||||
ax.get_title(), | ||||
ha='center', va='bottom', fontsize=16) | ||||
ax.set_title('') | ||||
Nlines = len(ax.lines) | ||||
lines = [xaxis, yaxis] + [ax.lines.pop(0) for i in range(Nlines)] | ||||
for line in lines: | ||||
x, y = line.get_data() | ||||
x_int, y_int = xkcd_line(x, y, xlim, ylim, | ||||
mag, f1, f2, f3) | ||||
# create foreground and background line | ||||
lw = line.get_linewidth() | ||||
line.set_linewidth(2 * lw) | ||||
line.set_data(x_int, y_int) | ||||
# don't add background line for axes | ||||
if (line is not xaxis) and (line is not yaxis): | ||||
line_bg = pl.Line2D(x_int, y_int, color=bgcolor, | ||||
linewidth=8 * lw) | ||||
ax.add_line(line_bg) | ||||
ax.add_line(line) | ||||
# Draw arrow-heads at the end of axes lines | ||||
arr1 = 0.03 * np.array([-1, 0, -1]) | ||||
arr2 = 0.02 * np.array([-1, 0, 1]) | ||||
arr1[::2] += np.random.normal(0, 0.005, 2) | ||||
arr2[::2] += np.random.normal(0, 0.005, 2) | ||||
x, y = xaxis.get_data() | ||||
if '+' in str(xaxis_arrow): | ||||
ax.plot(x[-1] + arr1 * xspan * aspect, | ||||
y[-1] + arr2 * yspan, | ||||
color='k', lw=2) | ||||
if '-' in str(xaxis_arrow): | ||||
ax.plot(x[0] - arr1 * xspan * aspect, | ||||
y[0] - arr2 * yspan, | ||||
color='k', lw=2) | ||||
x, y = yaxis.get_data() | ||||
if '+' in str(yaxis_arrow): | ||||
ax.plot(x[-1] + arr2 * xspan * aspect, | ||||
y[-1] + arr1 * yspan, | ||||
color='k', lw=2) | ||||
if '-' in str(yaxis_arrow): | ||||
ax.plot(x[0] - arr2 * xspan * aspect, | ||||
y[0] - arr1 * yspan, | ||||
color='k', lw=2) | ||||
# Change all the fonts to humor-sans. | ||||
prop = fm.FontProperties(fname='Humor-Sans.ttf', size=16) | ||||
for text in ax.texts: | ||||
text.set_fontproperties(prop) | ||||
# modify legend | ||||
leg = ax.get_legend() | ||||
if leg is not None: | ||||
leg.set_frame_on(False) | ||||
for child in leg.get_children(): | ||||
if isinstance(child, pl.Line2D): | ||||
x, y = child.get_data() | ||||
child.set_data(xkcd_line(x, y, mag=10, f1=100, f2=0.001)) | ||||
child.set_linewidth(2 * child.get_linewidth()) | ||||
if isinstance(child, pl.Text): | ||||
child.set_fontproperties(prop) | ||||
# Set the axis limits | ||||
ax.set_xlim(xax_lim[0] - 0.1 * xspan, | ||||
xax_lim[1] + 0.1 * xspan) | ||||
ax.set_ylim(yax_lim[0] - 0.1 * yspan, | ||||
yax_lim[1] + 0.1 * yspan) | ||||
# adjust the axes | ||||
ax.set_xticks([]) | ||||
ax.set_yticks([]) | ||||
if expand_axes: | ||||
ax.figure.set_facecolor(bgcolor) | ||||
ax.set_axis_off() | ||||
ax.set_position([0, 0, 1, 1]) | ||||
return ax | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\end{codecell} | ||||
\subsection{Testing it Out} | ||||
Let's test this out with a simple plot. We'll plot two curves, add some | ||||
labels, and then call \texttt{XKCDify} on the axis. I think the results | ||||
are pretty nice! | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
%pylab inline | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\begin{codeoutput} | ||||
\begin{verbatim} | ||||
Welcome to pylab, a matplotlib-based Python environment [backend: module://IPython.zmq.pylab.backend_inline]. | ||||
For more information, type 'help(pylab)'. | ||||
\end{verbatim} | ||||
\end{codeoutput} | ||||
\end{codecell} | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
np.random.seed(0) | ||||
ax = pylab.axes() | ||||
x = np.linspace(0, 10, 100) | ||||
ax.plot(x, np.sin(x) * np.exp(-0.1 * (x - 5) ** 2), 'b', lw=1, label='damped sine') | ||||
ax.plot(x, -np.cos(x) * np.exp(-0.1 * (x - 5) ** 2), 'r', lw=1, label='damped cosine') | ||||
ax.set_title('check it out!') | ||||
ax.set_xlabel('x label') | ||||
ax.set_ylabel('y label') | ||||
ax.legend(loc='lower right') | ||||
ax.set_xlim(0, 10) | ||||
ax.set_ylim(-1.0, 1.0) | ||||
#XKCDify the axes -- this operates in-place | ||||
XKCDify(ax, xaxis_loc=0.0, yaxis_loc=1.0, | ||||
xaxis_arrow='+-', yaxis_arrow='+-', | ||||
expand_axes=True) | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\begin{codeoutput} | ||||
\begin{verbatim} | ||||
<matplotlib.axes.AxesSubplot at 0x2fecbd0> | ||||
\end{verbatim} | ||||
\begin{center} | ||||
\includegraphics[width=0.7\textwidth]{XKCD_plots_orig_files/XKCD_plots_orig_fig_00.png} | ||||
\par | ||||
\end{center} | ||||
\end{codeoutput} | ||||
\end{codecell} | ||||
\subsection{Duplicating an XKCD Comic} | ||||
Now let's see if we can use this to replicated an XKCD comic in | ||||
matplotlib. This is a good one: | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
Image('http://imgs.xkcd.com/comics/front_door.png') | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\begin{codeoutput} | ||||
\begin{verbatim} | ||||
<IPython.core.display.Image at 0x2ff4a10> | ||||
\end{verbatim} | ||||
\end{codeoutput} | ||||
\end{codecell} | ||||
With the new \texttt{XKCDify} function, this is relatively easy to | ||||
replicate. The results are not exactly identical, but I think it | ||||
definitely gets the point across! | ||||
\begin{codecell} | ||||
\begin{codeinput} | ||||
\begin{lstlisting} | ||||
# Some helper functions | ||||
def norm(x, x0, sigma): | ||||
return np.exp(-0.5 * (x - x0) ** 2 / sigma ** 2) | ||||
def sigmoid(x, x0, alpha): | ||||
return 1. / (1. + np.exp(- (x - x0) / alpha)) | ||||
# define the curves | ||||
x = np.linspace(0, 1, 100) | ||||
y1 = np.sqrt(norm(x, 0.7, 0.05)) + 0.2 * (1.5 - sigmoid(x, 0.8, 0.05)) | ||||
y2 = 0.2 * norm(x, 0.5, 0.2) + np.sqrt(norm(x, 0.6, 0.05)) + 0.1 * (1 - sigmoid(x, 0.75, 0.05)) | ||||
y3 = 0.05 + 1.4 * norm(x, 0.85, 0.08) | ||||
y3[x > 0.85] = 0.05 + 1.4 * norm(x[x > 0.85], 0.85, 0.3) | ||||
# draw the curves | ||||
ax = pl.axes() | ||||
ax.plot(x, y1, c='gray') | ||||
ax.plot(x, y2, c='blue') | ||||
ax.plot(x, y3, c='red') | ||||
ax.text(0.3, -0.1, "Yard") | ||||
ax.text(0.5, -0.1, "Steps") | ||||
ax.text(0.7, -0.1, "Door") | ||||
ax.text(0.9, -0.1, "Inside") | ||||
ax.text(0.05, 1.1, "fear that\nthere's\nsomething\nbehind me") | ||||
ax.plot([0.15, 0.2], [1.0, 0.2], '-k', lw=0.5) | ||||
ax.text(0.25, 0.8, "forward\nspeed") | ||||
ax.plot([0.32, 0.35], [0.75, 0.35], '-k', lw=0.5) | ||||
ax.text(0.9, 0.4, "embarrassment") | ||||
ax.plot([1.0, 0.8], [0.55, 1.05], '-k', lw=0.5) | ||||
ax.set_title("Walking back to my\nfront door at night:") | ||||
ax.set_xlim(0, 1) | ||||
ax.set_ylim(0, 1.5) | ||||
# modify all the axes elements in-place | ||||
XKCDify(ax, expand_axes=True) | ||||
\end{lstlisting} | ||||
\end{codeinput} | ||||
\begin{codeoutput} | ||||
\begin{verbatim} | ||||
<matplotlib.axes.AxesSubplot at 0x2fef210> | ||||
\end{verbatim} | ||||
\begin{center} | ||||
\includegraphics[width=0.7\textwidth]{XKCD_plots_orig_files/XKCD_plots_orig_fig_01.png} | ||||
\par | ||||
\end{center} | ||||
\end{codeoutput} | ||||
\end{codecell} | ||||
Pretty good for a couple hours's work! | ||||
I think the possibilities here are pretty limitless: this is going to be | ||||
a hugely useful and popular feature in matplotlib, especially when the | ||||
sketch artist PR is mature and part of the main package. I imagine using | ||||
this style of plot for schematic figures in presentations where the | ||||
normal crisp matplotlib lines look a bit too ``scientific''. I'm giving | ||||
a few talks at the end of the month\ldots{} maybe I'll even use some of | ||||
this code there. | ||||
This post was written entirely in an IPython Notebook: the notebook file | ||||
is available for download | ||||
\href{http://jakevdp.github.com/downloads/notebooks/XKCD\_plots.ipynb}{here}. | ||||
For more information on blogging with notebooks in octopress, see my | ||||
\href{http://jakevdp.github.com/blog/2012/10/04/blogging-with-ipython/}{previous | ||||
post} on the subject. | ||||
\end{document} | ||||