Matplotlib: how to add a base image to a 3D plot?
Question: This topic has been touched
[here](http://stackoverflow.com/questions/23785408/3d-cartopy-similar-to-
matplotlib-basemap), but no indications were given as to how to create a 3D
plot and insert an image in the `(x,y)` plane, at a specified `z` height.

So to come up with a simple and reproducible case, let's say that I create a
3D plot like this with `mplot3d`:

    
    
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib import cm
    from matplotlib.ticker import LinearLocator, FormatStrFormatter
    import matplotlib.pyplot as plt
    import numpy as np
    
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    X = np.arange(-5, 5, 0.25)
    Y = np.arange(-5, 5, 0.25)
    X, Y = np.meshgrid(X, Y)
    R = np.sqrt(X**2 + Y**2)
    Z = np.sin(R)
    surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.winter,
                           linewidth=0, antialiased=True)
    ax.set_zlim(-1.01, 1.01)
    
    ax.zaxis.set_major_locator(LinearLocator(10))
    ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
    
    plt.show()
    

Visually we have: [![enter image description
here](http://i.stack.imgur.com/mnQTP.png)](http://i.stack.imgur.com/mnQTP.png)

**At the level`z=min(z)-1`**, where `-1` is a visual offset to avoid
overlapping, **I want to insert an image** representing the elements for which
the curve shows a certain value. **How to do it?**

In this example I don't care about a perfect matching between the element and
its value, so please feel free to upload any image you like. Also, is there a
way of letting that image rotate, in case one is not happy with the matching?

**EDIT**

This is a visual example of something similar made for a 3D histogram. The
grey shapes at the level `z=0` are the elements for which the bars show a
certain `z` value. [Source.](https://toeholds.wordpress.com/2010/03/26/3d-bar-
histogram-in-python/) [![enter image description
here](http://i.stack.imgur.com/ENvTK.png)](http://i.stack.imgur.com/ENvTK.png)


Answer: Use `plot_surface` to draw image via `facecolors` argument.

    
    
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib import cm
    from matplotlib.ticker import LinearLocator, FormatStrFormatter
    import matplotlib.pyplot as plt
    import numpy as np
    from matplotlib._png import read_png
    from matplotlib.cbook import get_sample_data
    
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    X = np.arange(-5, 5, .25)
    Y = np.arange(-5, 5, .25)
    X, Y = np.meshgrid(X, Y)
    R = np.sqrt(X**2 + Y**2)
    Z = np.sin(R)
    surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.winter,
                           linewidth=0, antialiased=True)
    
    ax.set_zlim(-2.01, 1.01)
    ax.zaxis.set_major_locator(LinearLocator(10))
    ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
    
    fn = get_sample_data("./lena.png", asfileobj=False)
    arr = read_png(fn)
    # 10 is equal length of x and y axises of your surface
    stepX, stepY = 10. / arr.shape[0], 10. / arr.shape[1]
    
    X1 = np.arange(-5, 5, stepX)
    Y1 = np.arange(-5, 5, stepY)
    X1, Y1 = np.meshgrid(X1, Y1)
    # stride args allows to determine image quality 
    # stride = 1 work slow
    ax.plot_surface(X1, Y1, -2.01, rstride=1, cstride=1, facecolors=arr)
    
    plt.show()
    

[![enter image description
here](http://i.stack.imgur.com/A0GCd.png)](http://i.stack.imgur.com/A0GCd.png)

If you need to add values use `PathPatch`:

    
    
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib import cm
    from matplotlib.ticker import LinearLocator, FormatStrFormatter
    import matplotlib.pyplot as plt
    import numpy as np
    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.patches import PathPatch
    
    def text3d(ax, xyz, s, zdir="z", size=None, angle=0, usetex=False, **kwargs):
        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)
    
    # main
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    X = np.arange(-5, 5, .25)
    Y = np.arange(-5, 5, .25)
    
    Xg, Yg = np.meshgrid(X, Y)
    R = np.sqrt(Xg**2 + Yg**2)
    Z = np.sin(R)
    surf = ax.plot_surface(Xg, Yg, Z, rstride=1, cstride=1, cmap=cm.winter,
                           linewidth=0, antialiased=True)
    
    ax.set_zlim(-2.01, 1.01)
    ax.zaxis.set_major_locator(LinearLocator(10))
    ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
    
    # add pathces with values
    for i,x in enumerate(X[::4]):
        for j,y in enumerate(Y[::4]):
            text3d(ax, (x, y, -2.01), "{0:.1f}".format(Z[i][j]), zdir="z", size=.5, ec="none", fc="k")
    
    plt.show()
    

[![enter image description
here](http://i.stack.imgur.com/kjmc1.png)](http://i.stack.imgur.com/kjmc1.png)

