Here is my modification of p479h's answer.
from typing import Optional, Tuple, Union
from matplotlib.text import Annotation
from matplotlib import pyplot as plt
from mpl_toolkits import mplot3d as plt3d
class Annotation3D(Annotation):
"""
Annotation for 3D axes
Args:
text: text to display as annotation
xyz: annotation co-ordinates
*args: passed to `matplotlib.text.Annotation`
**kwargs: passed to `matplotlib.text.Annotation`
"""
def __init__(self, text: str, xyz: Tuple[float, float, float], *args,
**kwargs):
Annotation.__init__(self, text, xy=(0, 0), *args, **kwargs)
# 3D position
self._xyz = xyz
# Hard-set 2D projection
self._xy: Optional[Tuple[float, float]] = None
@property
def xy(self):
if self._xy is not None:
return self._xy
*xy2d, _ = plt3d.proj3d.proj_transform(*self._xyz, self.axes.M)
return xy2d
@xy.setter
def xy(self, val):
# publicly set
self._xy = val
@xy.deleter
def xy(self):
self._xy = None
# replace ax.annotate(*args, **kwargs) by annotateND(ax, *args, **kwargs)
def annotateND(ax: Union[plt.Axes, plt3d.axes3d.Axes3D], text: str,
xy: Tuple[float, ...], *args, **kwargs) -> Union[Annotation3D, Annotation]:
"""
Add annotation to 3D axes
Args:
ax: target (parent) axes
text: Annotation text
xy: annotation co-ordinates
*args: passed to `matplotlib.text.Annotation`
**kwargs: passed to `matplotlib.text.Annotation`
Returns:
AnnotationD
"""
if isinstance(ax, plt3d.axes3d.Axes3D):
a = Annotation3D(text, xy, *args, **kwargs)
ax.add_artist(a)
return a
return ax.annotate(text, xy, *args, **kwargs)
A step towards seamlessly replacing all ax.annotate(*args, **kwargs)
with annotateND(ax, *args, **kwargs)
for 2D or 3D axes.