Ausgabe
Betrachten Sie die folgende Pandas-Serie s
und Handlung
import pandas as pd
import numpy as np
s = pd.Series(np.random.lognormal(.001, .01, 100))
ax = s.cumprod().plot()
ax.set_title('My Log Normal Example', position=(.5, 1.02),
backgroundcolor='black', color='white')
Wie bekomme ich die Box, die den Titel enthält, um die gesamte Handlung zu überspannen?
Lösung
Es ist natürlich möglich, den Begrenzungsrahmen des Titels zu erhalten, der ein Text
Element ist. Dies kann mit erfolgen
title = ax.set_title(...)
bb = title.get_bbox_patch()
Im Prinzip kann man dann die Begrenzungsbox manipulieren, zB über
bb.set_width(...)
. Allerdings gehen alle Einstellungen verloren, sobald matplotlib den Titel auf die Leinwand zeichnet. So interpretiere ich zumindest die Text
Methode von draw()
.
Mir sind keine anderen Methoden zum Festlegen des Begrenzungsrahmens bekannt. Zum Beispiel legend
kann der Begrenzungsrahmen von a so eingestellt werden
plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3, mode="expand")
, dass er sich über den gesamten Achsenbereich ausdehnt (siehe hier ). Es wäre sehr nützlich, die gleiche Option auch für zu haben Text
. Aber im Moment tun wir das nicht.
Das Text
Objekt ermöglicht das Festlegen eines bbox
Arguments, das normalerweise zum Festlegen des Stils des Begrenzungsrahmens gedacht ist. Es gibt keine Möglichkeit, die Grenzen des Begrenzungsrahmens festzulegen, aber es akzeptiert ein Wörterbuch der Eigenschaften des umgebenden Rahmens. Und eine der akzeptierten Eigenschaften ist eine boxstyle
. Standardmäßig ist dies ein square
, kann aber auf einen Kreis oder Pfeil oder andere seltsame Formen eingestellt werden.
Diese boxstyle
sind eigentlich der Schlüssel zu einer möglichen Lösung. Sie alle erben von BoxStyle._Base
und – wie am Ende des Anmerkungsleitfadens zu sehen ist – man kann eine benutzerdefinierte Form definieren, indem man Unterklassen bildet BoxStyle._Base
.
Die folgende Lösung basiert auf der Unterklassenbildung BoxStyle._Base
in der Weise, dass sie die Breite der Achsen als Argument akzeptiert und den Rechteckpfad des Titels so zeichnet, dass er genau diese Breite hat.
Als Bonus können wir einen Event-Handler registrieren, der diese Breite anpasst, sobald sie sich aufgrund einer Größenänderung des Fensters ändert.
Hier ist der Code:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import BoxStyle
class ExtendedTextBox(BoxStyle._Base):
"""
An Extended Text Box that expands to the axes limits
if set in the middle of the axes
"""
def __init__(self, pad=0.3, width=500.):
"""
width:
width of the textbox.
Use `ax.get_window_extent().width`
to get the width of the axes.
pad:
amount of padding (in vertical direction only)
"""
self.width=width
self.pad = pad
super(ExtendedTextBox, self).__init__()
def transmute(self, x0, y0, width, height, mutation_size):
"""
x0 and y0 are the lower left corner of original text box
They are set automatically by matplotlib
"""
# padding
pad = mutation_size * self.pad
# we add the padding only to the box height
height = height + 2.*pad
# boundary of the padded box
y0 = y0 - pad
y1 = y0 + height
_x0 = x0
x0 = _x0 +width /2. - self.width/2.
x1 = _x0 +width /2. + self.width/2.
cp = [(x0, y0),
(x1, y0), (x1, y1), (x0, y1),
(x0, y0)]
com = [Path.MOVETO,
Path.LINETO, Path.LINETO, Path.LINETO,
Path.CLOSEPOLY]
path = Path(cp, com)
return path
dpi = 80
# register the custom style
BoxStyle._style_list["ext"] = ExtendedTextBox
plt.figure(dpi=dpi)
s = pd.Series(np.random.lognormal(.001, .01, 100))
ax = s.cumprod().plot()
# set the title position to the horizontal center (0.5) of the axes
title = ax.set_title('My Log Normal Example', position=(.5, 1.02),
backgroundcolor='black', color='white')
# set the box style of the title text box toour custom box
bb = title.get_bbox_patch()
# use the axes' width as width of the text box
bb.set_boxstyle("ext", pad=0.4, width=ax.get_window_extent().width )
# Optionally: use eventhandler to resize the title box, in case the window is resized
def on_resize(event):
print "resize"
bb.set_boxstyle("ext", pad=0.4, width=ax.get_window_extent().width )
cid = plt.gcf().canvas.mpl_connect('resize_event', on_resize)
# use the same dpi for saving to file as for plotting on screen
plt.savefig(__file__+".png", dpi=dpi)
plt.show()
Für den Fall, dass jemand an einer leichteren Lösung interessiert ist, besteht auch die Möglichkeit, mit dem mutation_aspect
Begrenzungsrahmen des Titels herumzuspielen, der beim Zeichnen des Titels anscheinend unverändert bleibt. Während das mutation_aspect
selbst im Grunde nur die Höhe der Box verändert, kann man extrem große Paddings für die Box verwenden und mutation_aspect
auf eine sehr kleine Zahl setzen, so dass die Box am Ende in der Breite erweitert erscheint. Der klare Nachteil dieser Lösung ist, dass die Werte für Padding und Aspekt durch Versuch und Irrtum gefunden werden müssen und sich für unterschiedliche Schrift- und Zifferngrößen ändern. Bei mir bringen die Werte von mutation_aspect = 0.04
und pad=11.9
das gewünschte Ergebnis, aber auf anderen Systemen können sie natürlich anders sein.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
s = pd.Series(np.random.lognormal(.001, .01, 100))
ax = s.cumprod().plot()
title = ax.set_title('My Log Normal Example', position=(.5, 1.02),
backgroundcolor='black', color='white',
verticalalignment="bottom", horizontalalignment="center")
title._bbox_patch._mutation_aspect = 0.04
title.get_bbox_patch().set_boxstyle("square", pad=11.9)
plt.tight_layout()
plt.savefig(__file__+".png")
plt.show()
Beantwortet von – ImportanceOfBeingErnest
Antwort geprüft von – Candace Johnson (FixError Volunteer)