The Star Wars opening crawl in Matplotlib

(0 comments)

Matplotlib isn't the obvious choice for depicting the Star Wars opening crawl, but that isn't a reason not to try:

The complications here are converting the TextPath object into a PathPatch on the 3D axes, which can be done with the function text3d presented in this Matplotlib demonstration, and making the text fade out as it scrolls to the top: to achieve this, the each line's RGB colour components are multiplied by a factor which decreases to zero with increasing y.

The text to display is in the file python-zen.txt.

import matplotlib.pyplot as plt
from matplotlib.patches import Circle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
from matplotlib.text import TextPath
from matplotlib.transforms import Affine2D
from matplotlib import animation
import numpy as np

def text3d(ax, xyz, s, zdir="z", size=None, angle=0, usetex=False, **kwargs):
    '''

    Adapted from the Matplotlib demonstration at
    https://matplotlib.org/gallery/mplot3d/pathpatch3d.html

    Plots the string 's' on the axes 'ax', with position 'xyz', size 'size',
    and rotation angle 'angle'.  'zdir' gives the axis which is to be treated
    as the third dimension.  usetex is a boolean indicating whether the string
    should be interpreted as latex or not.  Any additional keyword arguments
    are passed on to transform_path.

    Note: zdir affects the interpretation of xyz.
    '''

    x, y, z = xyz
    if zdir == "y":
        xy1, z1 = (x, z), y
    elif zdir == "y":
        xy1, z1 = (y, z), x
    else:
        xy1, z1 = (x, y), z

    text_path = TextPath((0, 0), s, size=size, usetex=usetex)
    trans = Affine2D().rotate(angle).translate(xy1[0], xy1[1])
    p1 = PathPatch(trans.transform_path(text_path), **kwargs)
    ax.add_patch(p1)
    art3d.pathpatch_2d_to_3d(p1, z=z1, zdir=zdir)


fig = plt.figure(facecolor='k')
ax = fig.add_subplot(111, projection='3d', facecolor='k')

def split_into_lines(text, n):
    """Return text split into a list of lines of no more than n characters."""

    words = text.split()
    lines = []
    this_line = words[0]
    for word in words[1:]:
        if len(this_line) + len(word) + 1 <= n:
            this_line += ' ' + word
        else:
            lines.append(this_line)
            this_line = word
    lines.append(this_line)
    return lines

text = open('/Users/christian/temp/python-zen.txt').read()
text_lines = split_into_lines(text, 36)

# Star Wars yellow text
c = np.array((0.933, 0.835, 0.294))

def init_ax():
    """Initialize the Axes and view direction."""

    ax.view_init(elev=45, azim=-90)
    ax.set_xlim(0, 100)
    ax.set_ylim(0, 100)
    ax.set_zlim(0, 10)
    ax.axis('off')

def update(j):
    """Update the animation for frame j."""

    ax.clear()
    init_ax()
    for i, line in enumerate(text_lines):
        y = -15*i+j - 45
        if y > -40:
            # Fade the text out as approaches the "top" of the screen.
            rc = 1 - np.clip(y / 200, 0, 1)
            text3d(ax, (-40, y, 0), line, size=10, ec='none', fc=c*rc)

ani = animation.FuncAnimation(fig, update, 600, blit=False, interval=100, repeat=True)
plt.show()
Current rating: 5

Comments

Comments are pre-moderated. Please be patient and your comment will appear soon.

There are currently no comments

New Comment

required

required (not published)

optional

required