Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb8ea343

Browse files
committed
Merge pull request#4342 from sptm/feature3418
ENH : Implement auto-wrapping textcloses#3418
2 parents7f9fb69 +ada3f9a commitb8ea343

File tree

6 files changed

+244
-50
lines changed

6 files changed

+244
-50
lines changed

‎doc/users/whats_new/autowrap_text.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Auto-wrapping Text
2+
------------------
3+
Added the keyword argument "wrap" to Text, which automatically breaks long lines of text when being drawn.
4+
Works for any rotated text, different modes of alignment, and for text that are either labels or titles.
5+
6+
Example:
7+
plt.text(1, 1, "This is a really long string that should be wrapped so that it does not go outside the figure.", wrap=True)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Auto-wrapping text demo.
3+
"""
4+
importmatplotlib.pyplotasplt
5+
6+
fig=plt.figure()
7+
plt.axis([0,10,0,10])
8+
t="This is a really long string that I'd rather have wrapped so that it"\
9+
" doesn't go outside of the figure, but if it's long enough it will go"\
10+
" off the top or bottom!"
11+
plt.text(4,1,t,ha='left',rotation=15,wrap=True)
12+
plt.text(6,5,t,ha='left',rotation=15,wrap=True)
13+
plt.text(5,5,t,ha='right',rotation=-15,wrap=True)
14+
plt.text(5,10,t,fontsize=18,style='oblique',ha='center',
15+
va='top',wrap=True)
16+
plt.text(3,4,t,family='serif',style='italic',ha='right',wrap=True)
17+
plt.text(-1,0,t,ha='left',rotation=-15,wrap=True)
18+
19+
plt.show()

‎lib/matplotlib/tests/test_text.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,8 @@ def test_text_with_arrow_annotation_get_window_extent():
367367
headwidth=21
368368
fig,ax=plt.subplots(dpi=100)
369369
txt=ax.text(s='test',x=0,y=0)
370-
ann=ax.annotate('test',
370+
ann=ax.annotate(
371+
'test',
371372
xy=(0.0,50.0),
372373
xytext=(50.0,50.0),xycoords='figure pixels',
373374
arrowprops={
@@ -441,3 +442,38 @@ def test_empty_annotation_get_window_extent():
441442
eq_(points[1,0],0.0)
442443
eq_(points[1,1],50.0)
443444
eq_(points[0,1],50.0)
445+
446+
447+
@image_comparison(baseline_images=['basictext_wrap'],
448+
extensions=['png'])
449+
deftest_basic_wrap():
450+
fig=plt.figure()
451+
plt.axis([0,10,0,10])
452+
t="This is a really long string that I'd rather have wrapped so that" \
453+
" it doesn't go outside of the figure, but if it's long enough it" \
454+
" will go off the top or bottom!"
455+
plt.text(4,1,t,ha='left',rotation=15,wrap=True)
456+
plt.text(6,5,t,ha='left',rotation=15,wrap=True)
457+
plt.text(5,5,t,ha='right',rotation=-15,wrap=True)
458+
plt.text(5,10,t,fontsize=18,style='oblique',ha='center',
459+
va='top',wrap=True)
460+
plt.text(3,4,t,family='serif',style='italic',ha='right',wrap=True)
461+
plt.text(-1,0,t,ha='left',rotation=-15,wrap=True)
462+
463+
464+
@image_comparison(baseline_images=['fonttext_wrap'],
465+
extensions=['png'])
466+
deftest_font_wrap():
467+
fig=plt.figure()
468+
plt.axis([0,10,0,10])
469+
t="This is a really long string that I'd rather have wrapped so that" \
470+
" it doesn't go outside of the figure, but if it's long enough it" \
471+
" will go off the top or bottom!"
472+
plt.text(4,-1,t,fontsize=18,family='serif',ha='left',rotation=15,
473+
wrap=True)
474+
plt.text(6,5,t,family='sans serif',ha='left',rotation=15,wrap=True)
475+
plt.text(5,5,t,weight='light',ha='right',rotation=-15,wrap=True)
476+
plt.text(5,10,t,weight='heavy',ha='center',va='top',wrap=True)
477+
plt.text(3,4,t,family='monospace',ha='right',wrap=True)
478+
plt.text(-1,0,t,fontsize=14,style='italic',ha='left',rotation=-15,
479+
wrap=True)

‎lib/matplotlib/text.py

Lines changed: 181 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
importmath
1111
importwarnings
1212

13+
importcontextlib
14+
1315
importnumpyasnp
1416

1517
frommatplotlibimportcbook
@@ -42,6 +44,22 @@ def _process_text_args(override, fontdict=None, **kwargs):
4244
returnoverride
4345

4446

47+
@contextlib.contextmanager
48+
def_wrap_text(textobj):
49+
"""
50+
Temporarily inserts newlines to the text if the wrap option is enabled.
51+
"""
52+
iftextobj.get_wrap():
53+
old_text=textobj.get_text()
54+
try:
55+
textobj.set_text(textobj._get_wrapped_text())
56+
yieldtextobj
57+
finally:
58+
textobj.set_text(old_text)
59+
else:
60+
yieldtextobj
61+
62+
4563
# Extracted from Text's method to serve as a function
4664
defget_rotation(rotation):
4765
"""
@@ -105,6 +123,7 @@ def get_rotation(rotation):
105123
visible [True | False]
106124
weight or fontweight ['normal' | 'bold' | 'heavy' | 'light' |
107125
'ultrabold' | 'ultralight']
126+
wrap [True | False]
108127
x float
109128
y float
110129
zorder any number
@@ -175,6 +194,7 @@ def __init__(self,
175194
linespacing=None,
176195
rotation_mode=None,
177196
usetex=None,# defaults to rcParams['text.usetex']
197+
wrap=False,
178198
**kwargs
179199
):
180200
"""
@@ -198,6 +218,7 @@ def __init__(self,
198218
self.set_text(text)
199219
self.set_color(color)
200220
self.set_usetex(usetex)
221+
self.set_wrap(wrap)
201222
self._verticalalignment=verticalalignment
202223
self._horizontalalignment=horizontalalignment
203224
self._multialignment=multialignment
@@ -211,7 +232,7 @@ def __init__(self,
211232
self._linespacing=linespacing
212233
self.set_rotation_mode(rotation_mode)
213234
self.update(kwargs)
214-
#self.set_bbox(dict(pad=0))
235+
#self.set_bbox(dict(pad=0))
215236

216237
def__getstate__(self):
217238
d=super(Text,self).__getstate__()
@@ -514,7 +535,7 @@ def update_bbox_position_size(self, renderer):
514535
self._bbox_patch.set_transform(tr)
515536
fontsize_in_pixel=renderer.points_to_pixels(self.get_size())
516537
self._bbox_patch.set_mutation_scale(fontsize_in_pixel)
517-
#self._bbox_patch.draw(renderer)
538+
#self._bbox_patch.draw(renderer)
518539

519540
def_draw_bbox(self,renderer,posx,posy):
520541

@@ -587,6 +608,115 @@ def set_clip_on(self, b):
587608
super(Text,self).set_clip_on(b)
588609
self._update_clip_properties()
589610

611+
defget_wrap(self):
612+
"""
613+
Returns the wrapping state for the text.
614+
"""
615+
returnself._wrap
616+
617+
defset_wrap(self,wrap):
618+
"""
619+
Sets the wrapping state for the text.
620+
"""
621+
self._wrap=wrap
622+
623+
def_get_wrap_line_width(self):
624+
"""
625+
Returns the maximum line width for wrapping text based on the
626+
current orientation.
627+
"""
628+
x0,y0=self.get_transform().transform(self.get_position())
629+
figure_box=self.get_figure().get_window_extent()
630+
631+
# Calculate available width based on text alignment
632+
alignment=self.get_horizontalalignment()
633+
self.set_rotation_mode('anchor')
634+
rotation=self.get_rotation()
635+
636+
left=self._get_dist_to_box(rotation,x0,y0,figure_box)
637+
right=self._get_dist_to_box(
638+
(180+rotation)%360,
639+
x0,
640+
y0,
641+
figure_box)
642+
643+
ifalignment=='left':
644+
line_width=left
645+
elifalignment=='right':
646+
line_width=right
647+
else:
648+
line_width=2*min(left,right)
649+
650+
returnline_width
651+
652+
def_get_dist_to_box(self,rotation,x0,y0,figure_box):
653+
"""
654+
Returns the distance from the given points, to the boundaries
655+
of a rotated box in pixels.
656+
"""
657+
ifrotation>270:
658+
quad=rotation-270
659+
h1=y0/math.cos(math.radians(quad))
660+
h2= (figure_box.x1-x0)/math.cos(math.radians(90-quad))
661+
elifrotation>180:
662+
quad=rotation-180
663+
h1=x0/math.cos(math.radians(quad))
664+
h2=y0/math.cos(math.radians(90-quad))
665+
elifrotation>90:
666+
quad=rotation-90
667+
h1= (figure_box.y1-y0)/math.cos(math.radians(quad))
668+
h2=x0/math.cos(math.radians(90-quad))
669+
else:
670+
h1= (figure_box.x1-x0)/math.cos(math.radians(rotation))
671+
h2= (figure_box.y1-y0)/math.cos(math.radians(90-rotation))
672+
673+
returnmin(h1,h2)
674+
675+
def_get_rendered_text_width(self,text):
676+
"""
677+
Returns the width of a given text string, in pixels.
678+
"""
679+
w,h,d=self._renderer.get_text_width_height_descent(
680+
text,
681+
self.get_fontproperties(),
682+
False)
683+
returnmath.ceil(w)
684+
685+
def_get_wrapped_text(self):
686+
"""
687+
Return a copy of the text with new lines added, so that
688+
the text is wrapped relative to the parent figure.
689+
"""
690+
# Not fit to handle breaking up latex syntax correctly, so
691+
# ignore latex for now.
692+
ifself.get_usetex():
693+
returnself.get_text()
694+
695+
# Build the line incrementally, for a more accurate measure of length
696+
line_width=self._get_wrap_line_width()
697+
wrapped_str=""
698+
line=""
699+
700+
forwordinself.get_text().split(' '):
701+
# New lines in the user's test need to force a split, so that it's
702+
# not using the longest current line width in the line being built
703+
sub_words=word.split('\n')
704+
foriinrange(len(sub_words)):
705+
current_width=self._get_rendered_text_width(
706+
line+' '+sub_words[i])
707+
708+
# Split long lines, and each newline found in the current word
709+
ifcurrent_width>line_widthori>0:
710+
wrapped_str+=line+'\n'
711+
line=""
712+
713+
ifline=="":
714+
line=sub_words[i]
715+
else:
716+
line+=' '+sub_words[i]
717+
718+
returnwrapped_str+line
719+
590720
@allow_rasterization
591721
defdraw(self,renderer):
592722
"""
@@ -601,56 +731,58 @@ def draw(self, renderer):
601731

602732
renderer.open_group('text',self.get_gid())
603733

604-
bbox,info,descent=self._get_layout(renderer)
605-
trans=self.get_transform()
606-
607-
# don't use self.get_position here, which refers to text position
608-
# in Text, and dash position in TextWithDash:
609-
posx=float(self.convert_xunits(self._x))
610-
posy=float(self.convert_yunits(self._y))
734+
with_wrap_text(self)astextobj:
735+
bbox,info,descent=textobj._get_layout(renderer)
736+
trans=textobj.get_transform()
611737

612-
posx,posy=trans.transform_point((posx,posy))
613-
canvasw,canvash=renderer.get_canvas_width_height()
738+
# don't use textobj.get_position here, which refers to text
739+
# position in Text, and dash position in TextWithDash:
740+
posx=float(textobj.convert_xunits(textobj._x))
741+
posy=float(textobj.convert_yunits(textobj._y))
614742

615-
# draw the FancyBboxPatch
616-
ifself._bbox_patch:
617-
self._draw_bbox(renderer,posx,posy)
618-
619-
gc=renderer.new_gc()
620-
gc.set_foreground(self.get_color())
621-
gc.set_alpha(self.get_alpha())
622-
gc.set_url(self._url)
623-
self._set_gc_clip(gc)
624-
625-
ifself._bbox:
626-
bbox_artist(self,renderer,self._bbox)
627-
angle=self.get_rotation()
628-
629-
forline,wh,x,yininfo:
630-
ifnotnp.isfinite(x)ornotnp.isfinite(y):
631-
continue
632-
633-
mtext=selfiflen(info)==1elseNone
634-
x=x+posx
635-
y=y+posy
636-
ifrenderer.flipy():
637-
y=canvash-y
638-
clean_line,ismath=self.is_math_text(line)
639-
640-
ifself.get_path_effects():
641-
frommatplotlib.patheffectsimportPathEffectRenderer
642-
textrenderer=PathEffectRenderer(self.get_path_effects(),
643-
renderer)
644-
else:
645-
textrenderer=renderer
743+
posx,posy=trans.transform_point((posx,posy))
744+
canvasw,canvash=renderer.get_canvas_width_height()
745+
746+
# draw the FancyBboxPatch
747+
iftextobj._bbox_patch:
748+
textobj._draw_bbox(renderer,posx,posy)
749+
750+
gc=renderer.new_gc()
751+
gc.set_foreground(textobj.get_color())
752+
gc.set_alpha(textobj.get_alpha())
753+
gc.set_url(textobj._url)
754+
textobj._set_gc_clip(gc)
755+
756+
iftextobj._bbox:
757+
bbox_artist(textobj,renderer,textobj._bbox)
758+
angle=textobj.get_rotation()
759+
760+
forline,wh,x,yininfo:
761+
ifnotnp.isfinite(x)ornotnp.isfinite(y):
762+
continue
763+
764+
mtext=textobjiflen(info)==1elseNone
765+
x=x+posx
766+
y=y+posy
767+
ifrenderer.flipy():
768+
y=canvash-y
769+
clean_line,ismath=textobj.is_math_text(line)
770+
771+
iftextobj.get_path_effects():
772+
frommatplotlib.patheffectsimportPathEffectRenderer
773+
textrenderer=PathEffectRenderer(
774+
textobj.get_path_effects(),renderer)
775+
else:
776+
textrenderer=renderer
646777

647-
ifself.get_usetex():
648-
textrenderer.draw_tex(gc,x,y,clean_line,
649-
self._fontproperties,angle,mtext=mtext)
650-
else:
651-
textrenderer.draw_text(gc,x,y,clean_line,
652-
self._fontproperties,angle,
653-
ismath=ismath,mtext=mtext)
778+
iftextobj.get_usetex():
779+
textrenderer.draw_tex(gc,x,y,clean_line,
780+
textobj._fontproperties,angle,
781+
mtext=mtext)
782+
else:
783+
textrenderer.draw_text(gc,x,y,clean_line,
784+
textobj._fontproperties,angle,
785+
ismath=ismath,mtext=mtext)
654786

655787
gc.restore()
656788
renderer.close_group('text')

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp