Publication-quality figures with matplotlib and svgutils

Matplotlib is a decent Python library for creating publication-quality plots which offers a multitude of different plot types. However, one limitation of matplotlib is that creating complex layouts can be at times complicated.

Therefore, post-processing of plots is usually done in some other vector graphicseditor such as inkscape or Adobe Illustrator. The typical workflow is as following:

  1. Import and analyse data in Python
  2. Create figures in matplotlib
  3. Export figures to PDF/SVG
  4. Import figures to vector-graphics editor
  5. Arrange and edit figures manually
  6. Export the figure to PDF

As you probably see, the typical workflow is quite complicated. To make things worse you may need to repeat the process several times, when, for example, you want to include more data into the analysis. This includes manual editing and arranging the figure, which is obviously time consuming. Therefore it makes sense to try and automate the process. Here, I will describe an automatic workflow which completely resides on Python tools.

  1. Create plots

    First you need to create nice matplotlib-based plots you would like to compose your figure from. You may download the scripts I will use in the example from github repository: anscombe.py and sigmoid_fit.py.

    https://neuroscience.telenczuk.pl/wp-content/uploads/2011/04/sigmoid_fit9.png

    sigmoid_fit.py

    https://neuroscience.telenczuk.pl/wp-content/uploads/2011/04/anscombe9.png

    anscombe.py

  2. Export to SVG

    A nice feature of matplotlib is that it allows to export figure to Scalable Vector Graphics (SVG) which is an open vector format [1] understood by many applications (such as Inkscape, Adobe Illustrator or even web browsers). Not going too much into details, I will only say that SVG files are text files with special predefined tags (much alike HTML tags). You may try to open one of them in a text editor to find out what I mean.

  3. Arrange plots into composite figures

    Now, we would like to combine both plots into one figure and add some annotations (such as one-letter labels: A,B, etc.). To this end, I will use a small Python package I wrote with this purpose svgutils. It is written completely in Python and uses only standard libraries. You may download it from github.

    The basic operations are similar to what you would do in a vector graphics editor, but instead of using a mouse you will do some scripting (I am sure you love it as much as I do). It may take some more time at the beginning, but with the advantage that you will not have to repeat the process when, for some reason, you need to modify the plots you generated with matplotlib (to add more data or modify the parameters of your analysis, just to name a few reasons).

    An example script is shown and explained below:

    import svgutils.transform as sg
    import sys 
    
    #create new SVG figure
    fig = sg.SVGFigure("16cm", "6.5cm")
    
    # load matpotlib-generated figures
    fig1 = sg.fromfile('sigmoid_fit.svg')
    fig2 = sg.fromfile('anscombe.svg')
    
    # get the plot objects
    plot1 = fig1.getroot()
    plot2 = fig2.getroot()
    plot2.moveto(280, 0, scale=0.5)
    
    # add text labels
    txt1 = sg.TextElement(25,20, "A", size=12, weight="bold")
    txt2 = sg.TextElement(305,20, "B", size=12, weight="bold")
    
    # append plots and labels to figure
    fig.append([plot1, plot2])
    fig.append([txt1, txt2])
    
    # save generated SVG files
    fig.save("fig_final.svg")
  4. Convert to PDF/PNG

    After running the script, you may convert the output file to a format of your choice. To this end, you can use inkscape which can produce PNG and PDF files from SVG source. You can do that directly from command line without the need of opening the whole application:

    inkscape --export-pdf=fig_final.pdf fig_final.svg
    inkscape --export-png=fig_final.png fig_final.svg

    And here is the final result:

    https://neuroscience.telenczuk.pl/wp-content/uploads/2011/04/fig_final3.png

    Final publication-ready figure.

Now, whenever you need to re-do the plots you can simply re-run the above scripts. You can also automate the process by means of a build system, such as GNU make or similar. This part will be covered in some of the next tutorials from the series.

Good luck and happy plotting!

PS If you have a better/alternative method for creating your publication plots, I would be very interested in learning about it. Please comment or mail me!

[1] In case you do not know it, a vector format in contrast to other (raster) formats such as PNG, JPEG does not represent graphics as individual pixels, but rather as modifiable objects (lines, circles, points etc.). They usually offer better qualitiy for publication plots (PDF files are one of them) and are also editable.

15 thoughts on “Publication-quality figures with matplotlib and svgutils

  1. MS says

    Hi,

    I know that you can use tex or mathtext to render nicely with matplotlib. But lets consider a figure that was generated by matplotlib with latex (usetex: True) or mathtext (usetex: False), and I want to add labels in a postprocess using svgutils that blend in nicely with the present text.

    For this I am using the Inkscape extension textext, which allows to input latex-rendered text sequences. My question was, if you can invoke textext from svgutils.

  2. admin says

    Hi,

    No, unfortunantely you can’t add LaTeX math with svgutils yet. However, you could adapt the textext extension to work with svgutils. From my quick look at textext code, it seems that it uses various converters based either on pstoedit or pdf2svg. I guess that one could take one of the *Converter classes and integrate them with svgutils. I don’t have much time now, but if you are interested you can give it a try!

    Bartosz

  3. Aditya Gilra says

    I added the last line in the code below to transform.py : This sets a bounding box while displaying svg, else I get transparent borders around my svg figure.

    class SVGFigure(object):
    def __init__(self, width=None, height=None):
    self.root = etree.Element(SVG+”svg”,nsmap=NSMAP)
    self.root.set(“version”, “1.1”)
    if width or height:
    self.root.set(“width”, width)
    self.root.set(“height”, height)
    self.root.set(“viewbox”, ‘0 0 ‘+width+’ ‘+height)

  4. Derya says

    I want to use one SVGFigure object as a clipping mask for another. Is this possible with svgutils and/or reportlab? Thank you.

  5. admin says

    You can use clippath in SVG, but it is not yet implemented in svgutils. If you want to give it a try, please send a PR on github.

  6. Cantwell Carson says

    Because you asked, I have had great success with Veusz. I can generate all the vectors that I need for the plots and save them as .npz files. If I want, I can give the vectors names in the .npz file, as in:

    output = [[data_0],[data_1], … , [data_6]]
    names_list = [‘list’, ‘of’, ‘the’, ‘names’, ‘for’, ‘my’, ‘vectors’]
    names_dict = {}
    fname = ‘the_name_of_the_file.npz’
    for idx, keys in enumerate(names_list):
    names_dict[keys]= output[idx]
    np.savez(fname, **names_dict)

    These are then imported, with their names, into Veusz, where they can be used for plotting and exporting to various formats. I’ll say that the GUI for Veusz makes figure design much easier. The problem is that there are some kinds of figures, like line plots or 3d images, that Veusz doesn’t support, so I might use svgutils in those cases.

  7. Benjamin says

    Hi dear, I was trying to apply your methodology towards combining SVG figures. I am able to produce and save SVG files separately (as seen in the code below) but when I am running the script to combine three of them into one, the produced file is empty with the following error message:
    “Could not open “DMMD_MW_Size.svg” – unsupported image format. The file may be corrupt.”
    I am wondering if you could help me with that. Here is my code:

    import matplotlib.pyplot as plt
    import svgutils.transform as sg
    import sys

    #create new SVG figure
    fig = sg.SVGFigure()

    # load matpotlib-generated figures
    fig1 = sg.fromfile(‘DMMD.xy.svg’)
    fig2 = sg.fromfile(‘DMMD.xz.svg’)
    fig3 = sg.fromfile(‘DMMD.yz.svg’)

    # get the plot objects
    plot1 = fig1.getroot()
    plot2 = fig2.getroot()
    plot3 = fig3.getroot()
    #plot3.moveto(280, 0, scale=0.5)

    # add text labels
    #txt1 = sg.TextElement(25,20, “A”, size=12, weight=”bold”)
    #txt2 = sg.TextElement(305,20, “B”, size=12, weight=”bold”)

    # append plots and labels to figure
    fig.append([plot1, plot2, plot3])
    #fig.append([txt1, txt2])

    # save generated SVG files
    fig.save(“DMMD_MW_Size.svg”)
    plt.show()

  8. Huziy Oleksandr says

    Hi:

    is there a way to crop a figure using svgutils? I have combined 2 figures, one on top of the other and now I have white space at the bottom and from the right side of the combined figure. I would like to crop the figure to the size of one of the combined figure. fig.set_size(fig1.get_size()) does not help….

    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *