pythonmatplotlib

Loss of quality when plotting many lines with alpha<1 with matplotlib


I am trying to make a publication-quality graph that nicely shows the density of where randomly generated lines fall on a plot. However, when I try to save these plots in .svg format, there is always a loss in quality because I think the lines are being rasterized. Calling the set_rasterized function doesn't appear to help.

Is there a way to force matplotlib to save these lines in vector format, or at least save them at higher quality so the images don't look terrible when zoomed in? Saving as a .png with high DPI does not help either.

import matplotlib.pyplot as plt
import numpy as np

num_lines = 1000
np.random.seed(42)
xs = np.linspace(0, 10, 100).reshape(1,-1)
ys = xs*np.random.normal(1,1,(num_lines,1)) + np.random.normal(0, 1, (num_lines,100))
for y in ys:
    l = plt.plot(xs.flatten(), y, 'k', alpha=0.01)
    l[0].set_rasterized(False)

plt.savefig('ex.svg')

plt.show()

Example of zoomed in plot


Solution

  • You allude to this in your comment, so I'll add an example of how I've addressed this by manipulating linewidth rather than alpha to get a similar visualization of distribution that still has sharp lines.

    Here's a replication of your current approach:

    import matplotlib.pyplot as plt
    import numpy as np
    num_lines = 1000
    np.random.seed(42)
    xs = np.linspace(0, 10, 100).reshape(1,-1)
    ys = xs*np.random.normal(1,1,(num_lines,1)) + np.random.normal(0, 1, (num_lines,100))
    for y in ys:
        l = plt.plot(xs.flatten(), y, 'k', alpha=0.01)
        l[0].set_rasterized(False)
    plt.savefig('ex.svg')
    plt.show()
    

    enter image description here

    Here's an alternative -- I also try to explicitly tell matplotlib not to rasterize via ax.set_rasterization_zorder(None) (I believe this is the same as your l[0].set_rasterized(False) call).

    The difference is that I switch to manipulating linewidth rather than alpha. I think the effect is fairly similar.

    fig, ax = plt.subplots()
    ax.set_rasterization_zorder(None)
    
    num_lines = 1000
    np.random.seed(42)
    xs = np.linspace(0, 10, 100).reshape(1, -1)
    ys = xs * np.random.normal(1, 1, (num_lines, 1)) + np.random.normal(0, 1, (num_lines, 100))
    for y in ys:
        ax.plot(xs.flatten(), y, color='black', linewidth=0.01)
    fig.savefig('ex_width.svg', format='svg')
    

    enter image description here

    When you zoom way in, you can see that the alpha approach (left) is fuzzier than the linewidth approach (right):

    enter image description here