Skip to content

Commit

Permalink
ENH: geom_text + adjust_text
Browse files Browse the repository at this point in the history
  • Loading branch information
has2k1 committed Nov 25, 2024
1 parent f748614 commit 6af648b
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 23 deletions.
3 changes: 3 additions & 0 deletions doc/changelog.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ title: Changelog
- Got rid of a logging information about the fontsize that is recorded for all
plots that have a legend. ({{< issue 889 >}})

- When using [](:class:`~plotnine.geom_text`) with `adjust_text`, some sensible
default arrow properties will be applied.

## v0.14.2
(2024-11-21)

Expand Down
62 changes: 41 additions & 21 deletions plotnine/geoms/geom_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
from .geom import geom

if typing.TYPE_CHECKING:
from typing import Any
from typing import Any, Sequence

import pandas as pd
from matplotlib.axes import Axes
from matplotlib.offsetbox import DrawingArea
from matplotlib.text import Text

from plotnine import aes
from plotnine.coords.coord import coord
Expand Down Expand Up @@ -267,7 +268,7 @@ def draw_group(
else:
bbox = {}

texts = []
texts: Sequence[Text] = []

# For labels add a bbox
for i in range(len(data)):
Expand All @@ -282,31 +283,20 @@ def draw_group(
text_elem.set_path_effects(params["path_effects"])

# TODO: Do adjust text per panel
if _adjust := params["adjust_text"]:
from adjustText import adjust_text

if params["adjust_text"] is not None:
if params["zorder"] == 1:
warn(
"For better results with adjust_text, it should "
"not be the first layer or the only layer.",
PlotnineWarning,
)

_adjust = _adjust.copy()
arrowprops = _adjust.pop("arrowprops", {}).copy()
if "color" not in arrowprops:
arrowprops["color"] = color[0]

# The head_length, tail_length and tail_width of the arrow are
# specified on the same scale as the fontsize, but their default
# values are in the [0, 1] range. The true values are obtained by
# multiplying by the mutation_scale. The default value of
# mutation_scale is 1, so the arrow is effectively invisible.
# A good default for this usecase is the size of text.
if "mutation_scale" not in arrowprops:
arrowprops["mutation_scale"] = data["size"].mean()

adjust_text(texts, ax=ax, arrowprops=arrowprops, **_adjust)
do_adjust_text(
texts,
ax,
color[0],
float(data["size"].mean()),
params["adjust_text"],
)

@staticmethod
def draw_legend(
Expand Down Expand Up @@ -364,3 +354,33 @@ def check_adjust_text():
except ImportError as err:
msg = "To use adjust_text you must install the adjustText package."
raise PlotnineError(msg) from err


def do_adjust_text(
texts: Sequence[Text],
ax: Axes,
color: Any,
size: float,
params: dict[str, Any],
):
from adjustText import adjust_text

_default_params = {"expand": (1.5, 1.5)}
# The default arrowprops that are passed to
# matplotlib.patches.FancyArrowPatch
_default_arrowprops = {
"arrowstyle": "->",
"linewidth": 0.5,
"color": color,
# The head_length, tail_length and tail_width of the arrow are
# specified on the same scale as the fontsize, but their
# default values are in the [0, 1] range. The true values are
# obtained by multiplying by the mutation_scale. The default
# value of mutation_scale is 1, so the arrow is effectively
# invisible. A good default for this usecase is the size of
# text.
"mutation_scale": size,
}
params = _default_params | params
params["arrowprops"] = _default_arrowprops | params.get("arrowprops", {})
adjust_text(texts, ax=ax, **params)
3 changes: 1 addition & 2 deletions tests/test_geom_text_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@
)

adjust_text = {
"expand": (2, 2),
"arrowprops": {"arrowstyle": "->", "color": "red"},
"arrowprops": {"color": "red"},
}


Expand Down

0 comments on commit 6af648b

Please sign in to comment.