51

Is there a way in matplotlib to partially specify the color of a string?

Example:

plt.ylabel("Today is cloudy.")

How can I show "today" as red, "is" as green and "cloudy." as blue?

Thanks.

Gökhan Sever
  • 7,416
  • 12
  • 34
  • 37

3 Answers3

32

I only know how to do this non-interactively, and even then only with the 'PS' backend.

To do this, I would use Latex to format the text. Then I would include the 'color' package, and set your colors as you wish.

Here is an example of doing this:

import matplotlib
matplotlib.use('ps')
from matplotlib import rc

rc('text',usetex=True)
rc('text.latex', preamble='\usepackage{color}')
import matplotlib.pyplot as plt

plt.figure()
plt.ylabel(r'\textcolor{red}{Today} '+
           r'\textcolor{green}{is} '+
           r'\textcolor{blue}{cloudy.}')
plt.savefig('test.ps')

This results in (converted from ps to png using ImageMagick, so I could post it here): enter image description here

Yann
  • 31,093
  • 7
  • 76
  • 67
  • 2
    I would use this one, if only it were to work with the PDF backend :) For some reason, I can never get the axes placed properly on the canvas while I am working with the ps backend. – Gökhan Sever Feb 08 '12 at 17:03
  • I'm sorry - I didn't mean to downvote this. I meant to upvote it, and I must have misclicked earlier. – davidlowryduda Sep 09 '14 at 23:13
  • Very nice solution. Is there a way to create`pdf`'s? Aside from saving it as `ps` and then `ps2pdf`, which pretty much screws up everything in my graph... – magu_ Sep 17 '15 at 22:32
  • What a simple solution to this problem. If only it worked with more backends! – DanHickstein Oct 16 '15 at 15:01
  • Is it possible that this doesn't work anymore with newer versions of matplotlib (>=v3.0)? When I try this it doesn't change colours but displaces the yticklabels... – Zaus Aug 18 '20 at 01:09
  • 2
    With matplotlib 3.2.2 in py3, I had to escape the `\u` for unicode in `'\usepackage{color}'`. Then, I got `type1ec.sty' not found` error,,even after installing `texlive-extra`. –  Dec 09 '20 at 22:02
  • I have the same problem as @Zaus. All yticklabels get displaced randomly. – Camilo Martinez M. Apr 04 '21 at 04:36
  • This also helps if you want to get the coordinates of the ends of a string for plotting different strings individually: https://stackoverflow.com/questions/47538620/matplotlib-calculate-axis-coordinate-extents-given-string – Dance Party Apr 09 '21 at 21:08
  • @user989761 - See https://github.com/matplotlib/matplotlib/issues/16911 - On Debian and derivatives: `sudo apt install cm-super` - on macOS: `sudo tlmgr install cm-super` - on other systems: see packages listed at https://repology.org/projects/?search=cmsuper or https://repology.org/projects/?search=cm-super or https://repology.org/projects/?search=fontsrecommended – Samuel Lelièvre Dec 01 '21 at 12:52
  • This caused a runtime error for me when using Google Colab (not sure if it's their fault or if this code is broken). See [this GitHub issue](https://github.com/googlecolab/colabtools/issues/2589) for more – Drake P Jan 31 '22 at 07:49
26

Here's the interactive version. Edit: Fixed bug producing extra spaces in Matplotlib 3.

import matplotlib.pyplot as plt
from matplotlib import transforms

def rainbow_text(x,y,ls,lc,**kw):
    """
    Take a list of strings ``ls`` and colors ``lc`` and place them next to each
    other, with text ls[i] being shown in color lc[i].

    This example shows how to do both vertical and horizontal text, and will
    pass all keyword arguments to plt.text, so you can set the font size,
    family, etc.
    """
    t = plt.gca().transData
    fig = plt.gcf()
    plt.show()

    #horizontal version
    for s,c in zip(ls,lc):
        text = plt.text(x,y,s+" ",color=c, transform=t, **kw)
        text.draw(fig.canvas.get_renderer())
        ex = text.get_window_extent()
        t = transforms.offset_copy(text._transform, x=ex.width, units='dots')

    #vertical version
    for s,c in zip(ls,lc):
        text = plt.text(x,y,s+" ",color=c, transform=t,
                rotation=90,va='bottom',ha='center',**kw)
        text.draw(fig.canvas.get_renderer())
        ex = text.get_window_extent()
        t = transforms.offset_copy(text._transform, y=ex.height, units='dots')


plt.figure()
rainbow_text(0.05,0.05,"all unicorns poop rainbows ! ! !".split(), 
        ['red', 'orange', 'brown', 'green', 'blue', 'purple', 'black'],
        size=20)

enter image description here

divenex
  • 12,429
  • 9
  • 51
  • 51
Paul Ivanov
  • 1,894
  • 1
  • 16
  • 14
  • 2
    It looks like the words aren't exactly aligned in the vertical version. – Alex Oct 12 '16 at 19:37
  • 2
    This was actually a bug in matplotlib at the time I wrote that comment. It has since been fixed, as you can see [here](http://matplotlib.org/examples/text_labels_and_annotations/rainbow_text.html). – Paul Ivanov Oct 24 '16 at 23:52
  • Note that a figure does not necessarily have a canvas. E.g. in an object oriented setup, where a figure subclass might not be created with plt.figure().. – Marti Nito Feb 11 '19 at 18:34
  • Phenomenal solution – Novice Aug 13 '19 at 08:52
  • very helpful, just a small comment, if you save the figure with a different dpi, the renderer gives back the wrong scaling, so to make this work you need the dpi to be set before calling the rainbow_text function – p.py Feb 03 '21 at 12:50
10

Extending Yann's answer, LaTeX coloring now also works with PDF export:

import matplotlib
from matplotlib.backends.backend_pgf import FigureCanvasPgf
matplotlib.backend_bases.register_backend('pdf', FigureCanvasPgf)

import matplotlib.pyplot as plt

pgf_with_latex = {
    "text.usetex": True,            # use LaTeX to write all text
    "pgf.rcfonts": False,           # Ignore Matplotlibrc
    "pgf.preamble": [
        r'\usepackage{color}'     # xcolor for colours
    ]
}
matplotlib.rcParams.update(pgf_with_latex)

plt.figure()
plt.ylabel(r'\textcolor{red}{Today} '+
           r'\textcolor{green}{is} '+
           r'\textcolor{blue}{cloudy.}')
plt.savefig("test.pdf")

Note that this python script sometimes fails with Undefined control sequence errors in the first attempt. Running it again is then successful.

Felix
  • 589
  • 2
  • 8
  • 24