Styling matplotlib for news production

Corin, Pythondatavizmatplotlib
Back

As a data journalist at AFP, my daily workflow often uses Python for data analysis and exploratory visualization, then finishes by handing over an SVG to a graphic designer to be refined for publication.

As time goes on, I've been learning how to tweak Matplotlib styling to reduce the gap between what I export and a publishable (or near-publishable) graphic in our AFP house style.

So we can go from this:

Basic election chart

To this:

AFP styled election chart

Though matplotlib charts look very dry and academic out-of-the-box, I'm slowly coming to appreciate the level of control and flexibility available underneat the hood.

Here's a breakdown of the styling tweaks I'm currently using.

Set chart dimensions and line width

AFP's two-column graphic layout corresponds to a 6x4 image ratio, which we define before drawing the chart:

plt.figure(figsize=(6, 4))

We'll then draw a simple line chart following the example above –– US voter turnout over time. When we draw the chart we'll want to increase the line width slightly:

plt.plot(df['YEAR'],
         df['TURNOUT_RATE_PRES'],
          linewidth=2)

AFP election chart 1

Remove frame, calibrate axes

We're going to do a lot of the chart styling by modifying attributes of the Axes object.

First we call the .gca() function to get current axes of the plot and assign that to a variable. We can then use various methods of the axes api to modify plot characteristics – like setting the frame on or off.

ax = plt.gca()
ax.set_frame_on(False)

AFP election chart 2

We're also going to override the default y axis limits to make sure that we have a value tick above the highest value and below the lowest value of the line.

ax.set_ylim([0.44, 0.75])

While we're working on the y axis, we'll also want to change the decimal score to a percentage. We do this by first getting a reference to the y ticks, then using Python's format() function to display the decimal string as percent.

ticks = ax.get_yticks()
ax.set_yticklabels(['{:,.0%}'.format(x) for x in ticks])

AFP election chart 3

Style grid lines and ticks

AFP line charts and column charts generally use dashed full-width grid lines on the y axis of the chart.

First we'll switch off the y axis ticks, and tuck the x axis ticks closer to the chart. Then we'll draw a full width custom grid line in grey:

plt.tick_params(axis='y', left=False)
plt.tick_params(axis='x', length=5, direction='in', )

ax.grid(axis="y", 
        color="grey", 
        alpha=.5, 
        linewidth=.5, 
        linestyle="--")

AFP election chart 4

Define title, subtitle, and footnote

AFP charts are titled, usually use a subheading, and always include source attribution.

To match the house style we can use matplotlib's rcParams settings to choose a font, in this case Adobe's Source Sans. We'll also set svg.fonttype to none, which instructs matplotlib not to convert text into paths when saving an SVG.

plt.rcParams.update(
                {'font.family': 'Source Sans Pro',
                "svg.fonttype": 'none'
                })

Then we'll use plt.suptitle() ("supertitle") for the main heading, and title() for the subheading.

We need to set x and y values to position each of these correctly, though this can be confusing because each one uses a different reference system: suptitle() needs a y value of 1 or greater to appear at the top of the chart, whereas title() is at the top by default, and takes a pad argument to control vertical spacing.

Both should be horizontally aligned left rather than centered, which we set with the ha argument.

plt.suptitle("US election turnout",
             x=0.1,
             y=1.02,
             fontsize=20, 
             fontweight='bold',
             ha='left')
plt.title("Voter turnout in the 2024 election was still among the highest since 1900",
          x= -.03,
          pad=15,
          fontsize=10,
          fontweight='semibold',
          ha='left')

Lastly we use plt.figtext() to add a footnote with the source. As with suptitle(), we're giving [x,y] positioning coordinates from the bottom left of the figure.

plt.figtext(0.1, 0.01, 
            "Source: UF Election Lab", 
            ha="left", 
            fontsize=8)

And here's the finished result again:

AFP election chart final

For complete code to replicate this chart, see this GitHub gist.

© Corin Faife.RSS