How to create gradient text with a clipped background

Question:

I’m trying to draw gradient text. My idea is to create a gradient using the code from the docs to create a gradient background and then clip that image using a text path. For whatever reason, I’m only getting the first letter and some of the second.

enter image description here

Changing the limits of the axes or the width of the figure don’t seem to work. I can change the size of the TextPath, but then I can’t seem to make the text take up the full size of the image. Making the text size .2 and setting the ylim from 0 to .2 only makes squished text. How can I make the text span the size of the axes and take the full range of the gradient?

enter image description here

import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import matplotlib.textpath
import matplotlib.patches
import numpy as np


def gradient_image(ax, extent, direction=0.3, cmap_range=(0, 1), **kwargs):
    """
    Draw a gradient image based on a colormap.

    Parameters
    ----------
    ax : Axes
        The axes to draw on.
    extent
        The extent of the image as (xmin, xmax, ymin, ymax).
        By default, this is in Axes coordinates but may be
        changed using the *transform* keyword argument.
    direction : float
        The direction of the gradient. This is a number in
        range 0 (=vertical) to 1 (=horizontal).
    cmap_range : float, float
        The fraction (cmin, cmax) of the colormap that should be
        used for the gradient, where the complete colormap is (0, 1).
    **kwargs
        Other parameters are passed on to `.Axes.imshow()`.
        In particular useful is *cmap*.
    """
    phi = direction * np.pi / 2
    v = np.array([np.cos(phi), np.sin(phi)])
    X = np.array([[v @ [1, 0], v @ [1, 1]],
                  [v @ [0, 0], v @ [0, 1]]])
    a, b = cmap_range
    X = a + (b - a) / X.max() * X
    im = ax.imshow(X, extent=extent, interpolation='bicubic',
                   vmin=0, vmax=1, **kwargs)
    return im


fig, ax = plt.subplots(figsize=(1,1))

im = gradient_image(ax, direction=1, extent=(0, 1, 0, 1), transform=ax.transAxes,
               cmap=plt.cm.winter, cmap_range=(0.2, 0.8), alpha=0.5)

fp = FontProperties(family='DejaVu Sans', weight='bold')
text = matplotlib.textpath.TextPath((0.0, 0.0), 'ABCDEFG',
                                             size=1, prop=fp)

im.set_clip_path(text, transform=ax.transAxes)

# ax.set_xticks(())
# ax.set_yticks(())
# ax.tick_params(width=0, which='both')
# ax.set_xlim((0, 10))
# ax.set_ylim((0, 1))
for spine in ['top', 'bottom', 'left', 'right']:
    ax.spines[spine].set_visible(False)
Asked By: K. Shores

||

Answers:

I am not fully fluent in matplotlib transformations, but from what I’ve read, ax.transData seems to be the better approach here (comments were I changed your code):

enter image description here

import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import matplotlib.textpath
import matplotlib.patches
import numpy as np


def gradient_image(ax, extent, direction=0.3, cmap_range=(0, 1), **kwargs):
    """
    Draw a gradient image based on a colormap.

    Parameters
    ----------
    ax : Axes
        The axes to draw on.
    extent
        The extent of the image as (xmin, xmax, ymin, ymax).
        By default, this is in Axes coordinates but may be
        changed using the *transform* keyword argument.
    direction : float
        The direction of the gradient. This is a number in
        range 0 (=vertical) to 1 (=horizontal).
    cmap_range : float, float
        The fraction (cmin, cmax) of the colormap that should be
        used for the gradient, where the complete colormap is (0, 1).
    **kwargs
        Other parameters are passed on to `.Axes.imshow()`.
        In particular useful is *cmap*.
    """
    phi = direction * np.pi / 2
    v = np.array([np.cos(phi), np.sin(phi)])
    X = np.array([[v @ [1, 0], v @ [1, 1]],
                  [v @ [0, 0], v @ [0, 1]]])
    a, b = cmap_range
    X = a + (b - a) / X.max() * X
    # added origin = lower, elsewise text is flipped upside down
    im = ax.imshow(X, extent=extent, interpolation='bicubic',
                   vmin=0, vmax=1, origin='lower', **kwargs)
    return im


fig, ax = plt.subplots(figsize=(1, 1))

# define text before gradient to get extent
fp = FontProperties(family='DejaVu Sans', weight='bold')
text = matplotlib.textpath.TextPath((0.0, 0.0), 'ABCDEFG',
                                    size=1, prop=fp)
# use text to define imshow extent
extent = text.get_extents().extents[[0, 2, 1, 3]]
im = gradient_image(ax, direction=1, extent=extent,
                    cmap=plt.cm.winter, cmap_range=(0.2, 0.8), alpha=0.5)

# use transData instead of transAxes
im.set_clip_path(text, transform=ax.transData)
# © trenton
ax.spines[['top', 'bottom', 'left', 'right']].set_visible(False)

plt.show()
Answered By: Christian Karcher
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.