matplotlib / matplotlib

matplotlib: plotting with Python
https://matplotlib.org/stable/
19.76k stars 7.52k forks source link

[ENH]: Faking a text's metrics to fix its alignment #28211

Open anntzer opened 2 months ago

anntzer commented 2 months ago

Problem

Sometimes, it would be useful if we could "fake" a text's metrics to pretend it has a different physical size than its real size. Consider for example the following example; pretend the two subfigures are actually two separate figures that I want to save an embed in a presentation on consecutive slides, so that the orange line shows up on the second one:

from pylab import *
fig = figure(layout="constrained")
sfs = fig.subfigures(2)
ax0 = sfs[0].add_subplot()
ax0.plot([1, 2], label="foo")
sfs[0].legend(loc="outside right upper")
ax1 = sfs[1].add_subplot()
ax1.plot([1, 2], label="foo")
ax1.plot([2, 1], label="a longer label")
sfs[1].legend(loc="outside right upper")
show()

Figure_1 As it is, doing an animation would result in everything moving around in a rather ugly manner.

Because I know a bit about the matplotlib text internals :-) I know how to temporarily patch matplotlib to fix this, the trick being to swap out the text when matplotlib is measuring its extents, but not when drawing it:

diff --git i/lib/matplotlib/text.py w/lib/matplotlib/text.py
index 40cd8c8cd6..a042555ac1 100644
--- i/lib/matplotlib/text.py
+++ w/lib/matplotlib/text.py
@@ -363,6 +363,8 @@ class Text(Artist):
         """
         thisx, thisy = 0.0, 0.0
         lines = self._get_wrapped_text().split("\n")  # Ensures lines is not empty.
+        import sys
+        if lines == ["foo"] and sys._getframe().f_back.f_code.co_name != "draw": lines = ["a longer label"]

         ws = []
         hs = []

which results in Figure_1 but obviously that is not a "practical" solution (even though I'm happy to have that in my toolbox).

Proposed solution

At least for mathtext strings, we could support the TeX macros \smash, \phantom, and their variants, which are the TeX approach for fixing such alignment problems (see e.g. https://www.tug.org/TUGboat/Articles/tb22-4/tb72perlS.pdf). It is not entirely optimal to only support this for mathtext strings, but that's the best I can think of right now -- adding an additional "faked_size" (or similar) property on Text objects seems less convenient, e.g. for the legend case above.

timhoffm commented 2 months ago

Manipulating text metrics to monkey-patch layout is ok for TeX because basically all they have is text. But it feels much too specific for our case. I think we'd need more general tools to configure sizes and available space for Artists, likely something like Qt's sizeHint() / QSizePolicy mechanism.

If it's easy to support \smash / \phantom you can still do that as a particular feature of mathtext.