48

I have an index array (x) of dates (datetime objects) and an array of actual values (y: bond prices). Doing the following:

plot(x,y)

produces a perfectly fine time series graph with the x-axis labeled with the dates. No problem so far. But I want to add text on certain dates. For example, on 2009-10-31, I wish to display the text "Event 1" with an arrow pointing to the y value at that date.

I have read through the Matplotlib documentation on text() and annotate() to no avail.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
luffe
  • 1,588
  • 3
  • 21
  • 32

2 Answers2

87

Matplotlib uses an internal floating point format for dates.

You just need to convert your date to that format (using matplotlib.dates.date2num or matplotlib.dates.datestr2num) and then use annotate as usual.

As a somewhat excessively fancy example:

import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

x = [dt.datetime(2009, 05, 01), dt.datetime(2010, 06, 01), 
     dt.datetime(2011, 04, 01), dt.datetime(2012, 06, 01)]
y = [1, 3, 2, 5]

fig, ax = plt.subplots()
ax.plot_date(x, y, linestyle='--')

ax.annotate('Test', (mdates.date2num(x[1]), y[1]), xytext=(15, 15), 
            textcoords='offset points', arrowprops=dict(arrowstyle='-|>'))

fig.autofmt_xdate()
plt.show()

enter image description here

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 7
    This answer works like a dream but is a bit cumbersome (not Joe's fault). I've come across this blog post and realised that it's ok to just pass `xy=('2009-5-1',3)` in the above example and it works. Tested on matplotlib 2.2.2. https://jakevdp.github.io/PythonDataScienceHandbook/04.09-text-and-annotation.html – Aleksander Lidtke Aug 16 '18 at 18:36
  • 2
    @AleksanderLidtke Using a string like this only works if you are plotting from a pandas dataframe. It will not work if you plot from matplotlib directly. – Ted Petrou Mar 04 '20 at 20:50
  • Great answer! Is it me or `datetime.datetime(2010, 06, 01)` yields `SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers` and should be replaced by `datetime.datetime(2010, 6, 1)`? – PatrickT Aug 10 '22 at 01:21
1

Newer versions of matplotlib (e.g. 3.7.0) doesn't require explicit date2num calls anymore. Just pass a point on the plot directly as xy= to annotate().

from datetime import datetime
import matplotlib.pyplot as plt

x = [datetime(2009, 5, 1), datetime(2010, 6, 1), 
     datetime(2011, 4, 1), datetime(2012, 6, 1)]
y = [1, 3, 2, 5]

fig, ax = plt.subplots(facecolor='white')
ax.plot(x, y, linestyle='--')

ax.annotate('Test', xy=(x[1], y[1]),   # <---- directly pass the point position
            xytext=(-15, 15), textcoords='offset points', 
            arrowprops={'arrowstyle': '->'})

fig.autofmt_xdate()

img1

A more terse (but less flexible) solution is to use text() to annotate. Again, no need to perform a datetime-to-number conversion; just pass the point as is.

plt.plot(x, y, linestyle='--')
plt.text(x[1], y[1], 'Test')        # use x-y coordinate values as is
plt.xticks(plt.xticks()[0][::2]);

img2

cottontail
  • 10,268
  • 18
  • 50
  • 51