Skip to content

mackelab/figure_tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tutorial for making figures

This tutorial will cover my (@michaeldeistler) workflow for generating figures in Python. I was taught this workflow by @jan-matthis. The figures that are created in this tutorial follow the style of the ones in the following two papers:

You may also be interested in the Latex lab template for journal submissions or the sbi bibtex collection (both of these are private repositories for members of mackelab).

Here's an example of the figure created with the workflow presented in this repo:

Alt text

Goals

  • Consistent fontsizes, ticksizes, etc.
  • Generating figures completely from Python and the command line, without manually editing them in inkscape or illustrator.

Ingredients

We will go through the workflow in five parts. Note that all code that is described in these parts can also be found in this repo.

Part 0: Recommended filestructure (optional)
Part 1: Using a matplotlib stylefile
Part 2: Using svgutils==0.3.1 to compose multipanel figures
Part 3: Using invoke to convert svg to png or pdf
(Optional: Part 4: Syncing the files with overleaf and git from the commandline)

Part 0: Recommended filestructure (optional)

I recommend using the following filestructure within your repo:

├── paper
│   ├── fig1
│   │   ├── svg
│   │   ├── fig
│   │   ├── notebooks
│   │   │   ├── 01_assemble_figure.ipynb
│   │   │   ├── 01_assemble_appendix.ipynb
│   └── fig2
│   └── fig3
├── tasks.py
├── results
├── name_of_your_project
├── setup.py

All the code you import is in the name_of_your_project folder. The paper folder takes care of generating the figures. I recommend one subfolder for each figure (e.g. fig1). Each of these subfolders contains the notebooks that are used to generate the figure, an svg folder in which the individual panels will lie (see later), and a fig folder which contains the final figures that are used in your paper.

Part 1: Using a matplotlib stylefile

In order to make your figures look pretty, you can use a stylefile. This repo constains the stylefile which we used for the papers mentioned above (filename: .matplotlibrc). This file defines the linewidths, ticklengths and removes the top and right axes, etc. You can use the file as follows:

import matplotlib as mpl
import matplotlib.pyplot as plt

with mpl.rc_context(fname="../../../.matplotlibrc"):
    plt.plot([0.0, 1.0], [1.0, 2.0])

Part 2: Using svgutils to compose multipanel figures

You often want to be able to flexibly compose multi-panel figures and add small letters (a, b, ...) to the figure. You can do this with svgutils. Note that I use svgutils==0.3.1. The devs of svgutils made major changes in v0.3.2 in how panel-sizes are interpreted and I absolutely do not get along with their changes. Anyways...first, save each panel individually as svg:

import matplotlib as mpl
import matplotlib.pyplot as plt

with mpl.rc_context(fname="../../../.matplotlibrc"):
    fig, ax = plt.subplots(1, 1, figsize=(4, 3))
    plt.plot([0.0, 1.0], [1.0, 2.0])
    plt.savefig("../svg/panel_a.svg")
    
with mpl.rc_context(fname="../../../.matplotlibrc"):
    fig, ax = plt.subplots(1, 1, figsize=(4, 3))
    plt.plot([1.0, 2.0], [1.0, -2.0])
    plt.savefig("../svg/panel_b.svg")

Second, we use svgutils to compose the multipanel figure:

import time
import IPython.display as IPd
from svgutils.compose import *

def svg(img):
    IPd.display(IPd.HTML('<img src="{}" / >'.format(img, time.time())))

# > Inkscape pixel is 1/90 of an inch, other software usually uses 1/72.
# > http://www.inkscapeforum.com/viewtopic.php?f=6&t=5964
svg_scale = 1.25  # set this to 1.25 for Inkscape, 1.0 otherwise

# Panel letters in Helvetica Neue, 12pt, Medium
kwargs_text = {'size': '12pt', 'font': 'Arial', 'weight': '800'}

f = Figure("20.3cm", "7.1cm",
           
    Panel(
          SVG("../svg/panel_a.svg").scale(svg_scale),
          Text("a", -5, 2.0, **kwargs_text),
    ).move(10, 20),
    
    Panel(
          SVG("../svg/panel_b.svg").scale(svg_scale),
          Text("b", -5, 2.0, **kwargs_text),
    ).move(400, 20),
)

!mkdir -p fig
f.save("../fig/fig.svg")
svg("../fig/fig.svg")

Part 3: Using invoke to convert svg to png or pdf

We now have our final figure as svg file. For overleaf, we have to convert it to png or pdf. Of course, you could just open inkscape, import the svg, and export it as png. However, this is a bit tedious. Instead, we would like to do this from the commandline. I recommend using invoke to convert svg to png and pdf. Paste the following into a file tasks.py in the root folder of your repo and adapt the basepath variable:

from invoke import task
from pathlib import Path

basepath = "/path/to/your/repo"

open_cmd = "open"

fig_names = {
    "1": "paper/fig1",
    "2": "paper/fig2",
    "3": "paper/fig3",
}

@task
def convertpngpdf(c, fig):
    _convertsvg2pdf(c, fig)
    _convertpdf2png(c, fig)


########################################################################################
# Helpers
########################################################################################
@task
def _convertsvg2pdf(c, fig):
    if fig is None:
        for f in range(len(fig_names)):
            _convert_svg2pdf(c, str(f + 1))
        return
    pathlist = Path(f"{basepath}/{fig_names[fig]}/fig/").glob("*.svg")
    for path in pathlist:
        c.run(f"inkscape {str(path)} --export-pdf={str(path)[:-4]}.pdf")


@task
def _convertpdf2png(c, fig):
    if fig is None:
        for f in range(len(fig_names)):
            _convert_pdf2png(c, str(f + 1))
        return
    pathlist = Path(f"{basepath}/{fig_names[fig]}/fig/").glob("*.pdf")
    for path in pathlist:
        c.run(
            f'inkscape {str(path)} --export-png={str(path)[:-4]}.png -b "white" --export-dpi=250'
        )

Now, from the commandline, run invoke convertpngpdf 1. This will convert the svg of fig1 to pdf and png and save them in the fig folder.

Optional: Part 4: Syncing the files with overleaf and git from the commandline

Finally, you will want to upload the png and pdf to overleaf. Again, we would like to do this from the commandline. To do so, we can first use git to create a local copy of the overleaf project, see here. Second, we will add a task to our tasks.py file which will directly copy the files from our repository folder to the overleaf folder. Add the following to tasks.py and adapt the overleaf variable:

overleaf = "/path/to/your/overleaf"

@task
def syncoverleaf(c, fig):
    convertpngpdf(c, fig)
    c.run(f"cp {basepath}/{fig_names[fig]}/fig/*.pdf {overleaf}/figs/")
    c.run(f"cp {basepath}/{fig_names[fig]}/fig/*.png {overleaf}/figs/")

Now, from the command-line, you can run invoke syncoverleaf 1 and it will convert your svg to pdf and png and copy the files to the overleaf folder. Finally, you only have to commit your changes and upload to overleaf:

cd /path/to/your/overleaf
git commit -am "new figures"
git push

That's it! Your pretty multipanel figures are now on overleaf.

About

Tutorial for making figures, written by Michael @michaeldeistler

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published