Here I present two solutions. The first one is fast solution, will bloat (just a little) the SVG size, but will yield better looking results than using a raster image. The second one produces what you'd expect for an SVG with gradients, but it is slower.
First option
As matplotlib currently does not support gradients (see here), you can mimic a gradient using a not so large number of semitransparent rectangles. You can choose how many rectangles to use, where the transparency ends, and the color of the transparency:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
def add_veil(ax, top, n_steps=20, color='white'):
starts, height = np.linspace(0, top, n_steps, endpoint=False, retstep=True)
rects = [Rectangle((0, s), 1, height, facecolor=color, zorder=2.01,
alpha=(1-(n_a/n_steps)), transform=ax.transAxes)
for n_a, s in enumerate(starts)]
for r in rects:
ax.add_patch(r)
plt.figure(figsize=[6, 6])
x = np.arange(0, 100, 0.00001)
y = np.sin(0.1* np.pi * x)
plt.plot(x, y)
plt.axis('off')
ax = plt.gca()
ax.set_position([0, 0, 1, 1])
# DONE: Add white -> transparent layer
add_veil(ax, 1, 100)
plt.savefig("temp.svg", bbox_inches=0)
Result:

I had very good results with 20 rectangles or less.
I guess you can adapt it yo use a non linear gradient, which usually looks better.
Second option
The second option saves the SVG to memory, parses the XML, edits it and saves it. It is much slower since it has to parse the data. I made a class, so the data used for defining the gradient is generated only once.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import xml.etree.ElementTree as ET
import matplotlib
from io import BytesIO
class GradientAdder():
def __init__(self):
# First, define a linear gradient.
lingrad = ET.Element('linearGradient')
lingrad.set('id', "grad1")
lingrad.set('x1', "0%")
lingrad.set('y1', "100%")
lingrad.set('x2', "0%")
lingrad.set('y2', "0%")
stop1 = ET.Element('stop')
stop1.set('offset',"0%")
stop1.set('style', "stop-color:rgb(255,255,255);stop-opacity:1")
stop2 = ET.Element('stop')
stop2.set('offset',"100%")
stop2.set('style', "stop-color:rgb(255,255,255);stop-opacity:0")
lingrad.append(stop1)
lingrad.append(stop2)
self._defs = ET.Element('defs')
self._defs.append(lingrad)
self._defs.set('id', 'veilid')
def __call__(self, ax: matplotlib.axes.Axes, out_file: str):
rect = Rectangle((0, 0), 1, 1, zorder=2.01, color=(1,1,1,0),
transform=ax.transAxes)
ax.add_patch(rect)
rect.set_gid("whiteVeil") # We create a named patch
# Save SVG in a fake file object.
f = BytesIO()
ax.figure.savefig(f, format="svg", bbox_inches=0)
# This next line will break someday
ET.register_namespace('', "http://www.w3.org/2000/svg")
root, xmlid = ET.XMLID(f.getvalue())
root.append(self._defs)
rect = xmlid['whiteVeil'][0]
rect.set('style', "fill:url(#grad1);fill-opacity:1") #
ET.ElementTree(root).write(out_file)
g = GradientAdder()
x = np.arange(0, 100, 0.00001)
y = np.sin(0.1* np.pi * x)
plt.figure(figsize=[6, 6])
plt.plot(x, y)
plt.axis('off')
ax = plt.gca()
ax.set_position([0, 0, 1, 1])
g(ax, "/tmp/test.svg")
With this option, ypou have to stick to SVG's linear gradients. Note that it adds a transparent Rectangle over your figure
Other option
To obtain best results you can add a Rectangle patch over the Axes, and then fill it with a gradient using Inkscape in headless mode. See here and here. The documentation is scattered and missing.