reproducing xkcd's self-description comic with python
original:
this code:


from matplotlib import pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import matplotlib.image as mpimg
import os.path
from os.path import join
import numpy as np
import gc
import imageio
panelsize = 6,4
fontsize=26
spinesize=2
emptyimg = np.ones((panelsize[0]*300,panelsize[1]*100,4), dtype=float)
def normalize(l):
return l / max(l) if not max(l) == 0 else l
def pctblack(img):
return 1-img.mean()/255
## todo create context manager
def plotf(f, figsize=panelsize, nrows=1, ncols=1, xkcd=True):
def g():
fig, ax = plt.subplots(figsize=figsize, nrows=nrows, ncols=ncols)
width, height = fig.get_size_inches() * fig.get_dpi()
canvas = FigureCanvas(fig)
f(fig, ax)
canvas.draw()
img = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')
plt.close()
gc.collect()
return img.reshape(int(height), int(width), 3)
if xkcd:
with plt.xkcd(length=200):
img = g()
else:
img = g()
return img
def pie(pct):
def f(fig, ax):
wedges, texts = plt.pie(
[pct, 1-pct],
colors=['black','white'],
wedgeprops={"edgecolor":"black",
'linewidth': 3, 'antialiased': True},
startangle=-165,
explode=[0,0])
bbox_props = dict( fc="w", ec="k", lw=0.72)
kw = dict(arrowprops=dict(arrowstyle="-"),
zorder=990, va="center",size=fontsize, weight='bold'
)
labels = [f'FRACTION OF\nTHIS IMAGE\nWHICH IS {c}' for c in ['BLACK', 'WHITE']]
for i, p in enumerate(wedges):
if i==0:
ang = (p.theta2 - p.theta1)/2. + p.theta1
else:
ang = 150
y = np.sin(np.deg2rad(ang))
x = np.cos(np.deg2rad(ang))
connectionstyle = "angle,angleA=0,angleB={}".format(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
plt.annotate(labels[i], xy=(x,y),
xytext=(2.8*np.sign(x), 2*y), **kw)
plt.tight_layout()
fig.subplots_adjust(left=0.4, bottom=0.01, top=0.99)
return plotf(f)
def bar(l):
l = normalize(np.array(l))
def f(fig,ax):
plt.bar(range(1,4), l, color='black', width=.37)
ax.set_xticks(range(1,4))
ax.set_xticklabels(range(1,4), fontsize=fontsize)
ax.set_yticks([], [])
ax.set_xlim(0,4)
ax.set_ylim(0,max(max(l)*1.2,0.1))
ax.set_title('AMOUNT OF\nBLACK INK\nBY PANEL:',
loc='left', fontdict={'fontsize': fontsize, 'fontweight': 'bold'})
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
[ax.spines[e].set_linewidth(spinesize) for e in ["bottom","left"]]
plt.tight_layout()
fig.subplots_adjust(bottom=0.15, left=0.1,
right=0.9, top=0.7)
return plotf(f)
def location(img):
border = 100
def f(fig,ax):
plt.imshow(np.flip(img, axis=0))
xmin, xmax = -border, img.shape[1]+border
ymin, ymax = -border, img.shape[0]+border
ax.set_xlim(xmin,xmax+1)
ax.set_ylim(ymin,ymax+1)
ax.set_xticks([0])
ax.set_xticklabels([0], fontsize=20)
ax.set_yticks([0])
ax.set_yticklabels([0], fontsize=20)
ax.set_title('LOCATION OF\nBLACK INK IN\nTHIS IMAGE:', loc='left', fontdict={'fontsize': fontsize, 'fontweight': 'bold'})
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
[ax.spines[e].set_linewidth(spinesize) for e in ["bottom","left"]]
plt.tight_layout()
return plotf(f)
if __name__ == '__main__':
images = []
for k in range(25):
if len(images):
img = images[-1]
pct = pctblack(img)
print(f' {k}: {pct*100:.4f}%', end='\r')
if (len(images) > 1 and (abs(pctblack(images[-2]) - pct) < 0.00000001)
or len(images) > 2 and (pctblack(images[-3]) == pct)):
break
else:
pct = 0
img = emptyimg
indices = ((np.array(range(2))+1) * img.shape[1] / 3).astype(int)
ll = np.split(img, indices, 1)
imgbar = bar([pctblack(e) for e in ll])
imgpie = pie(pct)
imgloc = location(img)
def f(fig, ax):
def plotpanel(i, img):
ax[i].imshow(img)
ax[i].set_xticks([])
ax[i].set_yticks([])
[i.set_linewidth(spinesize) for i in ax[i].spines.values()]
for i, img in enumerate([imgpie,imgbar,imgloc]):
plotpanel(i, img)
plt.tight_layout()
images.append(plotf(f, xkcd=False, ncols=3, figsize=(panelsize[0]*3,panelsize[1])))
imageio.mimsave('plot.gif', images, duration=0.8)