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

Appearance settings

Commit 4ef9115

Browse filesBrowse files
committed
Merge pull request #1081 from pwuertz/draw_text_with_mtext
Propagate mpl.text.Text instances to the backends and fix documentation
2 parents dd92a43 + c814997 commit 4ef9115
Copy full SHA for 4ef9115

16 files changed

+116
-96
lines changed

‎CHANGELOG

Copy file name to clipboardExpand all lines: CHANGELOG
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
2012-11-27 Added the *mtext* parameter for supplying matplotlib.text.Text
2+
instances to RendererBase.draw_tex and RendererBase.draw_text.
3+
This allows backends to utilize additional text attributes, like
4+
the alignment of text elements. - pwuertz
5+
16
2012-11-16 plt.set_cmap no longer throws errors if there is not already
27
an active colorable artist, such as an image, and just sets
38
up the colormap to use from that point forward. - PI

‎doc/users/whats_new.rst

Copy file name to clipboardExpand all lines: doc/users/whats_new.rst
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ the whole figure. This was already the behavior for both
4141
:func:`~matplotlib.pyplot.axes` and :func:`~matplotlib.pyplot.subplots`, and
4242
now this consistency is shared with :func:`~matplotlib.pyplot.subplot`.
4343

44+
Anchored text support
45+
---------------------
46+
The `svg` and `pgf` backends are now able to save text alignment information
47+
to their output formats. This allows to edit text elements in saved figures,
48+
using Inkscape for example, while preserving their intended position. For
49+
`svg` please note that you'll have to disable the default text-to-path
50+
conversion (`mpl.rc('svg', fonttype='none')`).
51+
4452
.. _whats-new-1-2:
4553

4654
new in matplotlib-1.2

‎lib/matplotlib/axis.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axis.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,9 +1847,10 @@ def _get_label(self):
18471847
size=rcParams['axes.labelsize'],
18481848
weight=rcParams['axes.labelweight']),
18491849
color=rcParams['axes.labelcolor'],
1850-
verticalalignment='center',
1851-
horizontalalignment='right',
1850+
verticalalignment='bottom',
1851+
horizontalalignment='center',
18521852
rotation='vertical',
1853+
rotation_mode='anchor',
18531854
)
18541855
label.set_transform(mtransforms.blended_transform_factory(
18551856
mtransforms.IdentityTransform(), self.axes.transAxes))

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+6-3Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,12 +443,12 @@ def option_scale_image(self):
443443
"""
444444
return False
445445

446-
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!'):
446+
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
447447
"""
448448
"""
449449
self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX")
450450

451-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
451+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
452452
"""
453453
Draw the text instance
454454
@@ -462,14 +462,17 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
462462
the y location of the text in display coords
463463
464464
*s*
465-
a :class:`matplotlib.text.Text` instance
465+
the text string
466466
467467
*prop*
468468
a :class:`matplotlib.font_manager.FontProperties` instance
469469
470470
*angle*
471471
the rotation angle in degrees
472472
473+
*mtext*
474+
a :class:`matplotlib.text.Text` instance
475+
473476
**backend implementers note**
474477
475478
When you are trying to determine if you have gotten your bounding box

‎lib/matplotlib/backends/backend_agg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_agg.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
158158
y = int(y) - oy
159159
self._renderer.draw_text_image(font_image, x, y + 1, angle, gc)
160160

161-
def draw_text(self, gc, x, y, s, prop, angle, ismath):
161+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
162162
"""
163163
Render the text
164164
"""
@@ -215,7 +215,7 @@ def get_text_width_height_descent(self, s, prop, ismath):
215215
return w, h, d
216216

217217

218-
def draw_tex(self, gc, x, y, s, prop, angle):
218+
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
219219
# todo, handle props, angle, origins
220220
size = prop.get_size_in_points()
221221

‎lib/matplotlib/backends/backend_cairo.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_cairo.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def draw_image(self, gc, x, y, im):
177177

178178
im.flipud_out()
179179

180-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
180+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
181181
# Note: x,y are device/display coords, not user-coords, unlike other
182182
# draw_* methods
183183
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))

‎lib/matplotlib/backends/backend_emf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_emf.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ def draw_rectangle(self, gcEdge, rgbFace, x, y, width, height):
358358
if debugPrint: print("draw_rectangle: optimizing away (%f,%f) w=%f,h=%f" % (x,y,width,height))
359359

360360

361-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
361+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
362362
"""
363363
Draw the text.Text instance s at x,y (display coords) with font
364364
properties instance prop at angle in degrees, using GraphicsContext gc

‎lib/matplotlib/backends/backend_gdk.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_gdk.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def draw_image(self, gc, x, y, im):
138138
im.flipud_out()
139139

140140

141-
def draw_text(self, gc, x, y, s, prop, angle, ismath):
141+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
142142
x, y = int(x), int(y)
143143

144144
if x < 0 or y < 0: # window has shrunk and text is off the edge

‎lib/matplotlib/backends/backend_macosx.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_macosx.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def draw_image(self, gc, x, y, im):
111111
*gc.get_clip_path())
112112
im.flipud_out()
113113

114-
def draw_tex(self, gc, x, y, s, prop, angle):
114+
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
115115
# todo, handle props, angle, origins
116116
size = prop.get_size_in_points()
117117
texmanager = self.get_texmanager()
@@ -128,7 +128,7 @@ def _draw_mathtext(self, gc, x, y, s, prop, angle):
128128
self.mathtext_parser.parse(s, self.dpi, prop)
129129
gc.draw_mathtext(x, y, angle, 255 - image.as_array())
130130

131-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
131+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
132132
if ismath:
133133
self._draw_mathtext(gc, x, y, s, prop, angle)
134134
else:

‎lib/matplotlib/backends/backend_pdf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pdf.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,7 +1671,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
16711671
# Pop off the global transformation
16721672
self.file.output(Op.grestore)
16731673

1674-
def draw_tex(self, gc, x, y, s, prop, angle):
1674+
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
16751675
texmanager = self.get_texmanager()
16761676
fontsize = prop.get_size_in_points()
16771677
dvifile = texmanager.make_dvi(s, fontsize)
@@ -1763,7 +1763,7 @@ def encode_string(self, s, fonttype):
17631763
return s.encode('cp1252', 'replace')
17641764
return s.encode('utf-16be', 'replace')
17651765

1766-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
1766+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
17671767
# TODO: combine consecutive texts into one BT/ET delimited section
17681768

17691769
# This function is rather complex, since there is no way to

‎lib/matplotlib/backends/backend_pgf.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_pgf.py
+35-59Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -595,30 +595,53 @@ def draw_image(self, gc, x, y, im):
595595
writeln(self.fh, r"\pgftext[at=\pgfqpoint{%fin}{%fin},left,bottom]{\pgfimage[interpolate=true,width=%fin,height=%fin]{%s}}" % (x * f, y * f, w * f, h * f, fname_img))
596596
writeln(self.fh, r"\end{pgfscope}")
597597

598-
def draw_tex(self, gc, x, y, s, prop, angle, ismath="TeX!"):
599-
self.draw_text(gc, x, y, s, prop, angle, ismath)
598+
def draw_tex(self, gc, x, y, s, prop, angle, ismath="TeX!", mtext=None):
599+
self.draw_text(gc, x, y, s, prop, angle, ismath, mtext)
600600

601-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
601+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
602+
# prepare string for tex
602603
s = common_texification(s)
603-
604-
# apply font properties
605604
prop_cmds = _font_properties_str(prop)
606605
s = ur"{%s %s}" % (prop_cmds, s)
607606

608-
# draw text at given coordinates
609-
x = x * 1. / self.dpi
610-
y = y * 1. / self.dpi
607+
611608
writeln(self.fh, r"\begin{pgfscope}")
609+
612610
alpha = gc.get_alpha()
613611
if alpha != 1.0:
614612
writeln(self.fh, r"\pgfsetfillopacity{%f}" % alpha)
615613
writeln(self.fh, r"\pgfsetstrokeopacity{%f}" % alpha)
616-
stroke_rgb = tuple(gc.get_rgb())[:3]
617-
if stroke_rgb != (0, 0, 0):
618-
writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % stroke_rgb)
614+
rgb = tuple(gc.get_rgb())[:3]
615+
if rgb != (0, 0, 0):
616+
writeln(self.fh, r"\definecolor{textcolor}{rgb}{%f,%f,%f}" % rgb)
619617
writeln(self.fh, r"\pgfsetstrokecolor{textcolor}")
620618
writeln(self.fh, r"\pgfsetfillcolor{textcolor}")
621-
writeln(self.fh, "\\pgftext[left,bottom,x=%fin,y=%fin,rotate=%f]{%s}\n" % (x, y, angle, s))
619+
620+
f = 1.0 / self.figure.dpi
621+
text_args = []
622+
if angle == 0 or mtext.get_rotation_mode() == "anchor":
623+
# if text anchoring can be supported, get the original coordinates
624+
# and add alignment information
625+
x, y = mtext.get_transform().transform_point(mtext.get_position())
626+
text_args.append("x=%fin" % (x * f))
627+
text_args.append("y=%fin" % (y * f))
628+
629+
halign = {"left": "left", "right": "right", "center": ""}
630+
valign = {"top": "top", "bottom": "bottom",
631+
"baseline": "base", "center": ""}
632+
text_args.append(halign[mtext.get_ha()])
633+
text_args.append(valign[mtext.get_va()])
634+
else:
635+
# if not, use the text layout provided by matplotlib
636+
text_args.append("x=%fin" % (x * f))
637+
text_args.append("y=%fin" % (y * f))
638+
text_args.append("left")
639+
text_args.append("bottom")
640+
641+
if angle != 0:
642+
text_args.append("rotate=%f" % angle)
643+
644+
writeln(self.fh, r"\pgftext[%s]{%s}" % (",".join(text_args), s))
622645
writeln(self.fh, r"\end{pgfscope}")
623646

624647
def get_text_width_height_descent(self, s, prop, ismath):
@@ -861,53 +884,6 @@ def print_png(self, fname_or_fh, *args, **kwargs):
861884
else:
862885
raise ValueError("filename must be a path or a file-like object")
863886

864-
def _render_texts_pgf(self, fh):
865-
# TODO: currently unused code path
866-
867-
# alignment anchors
868-
valign = {"top": "top", "bottom": "bottom", "baseline": "base", "center": ""}
869-
halign = {"left": "left", "right": "right", "center": ""}
870-
# alignment anchors for 90deg. rotated labels
871-
rvalign = {"top": "left", "bottom": "right", "baseline": "right", "center": ""}
872-
rhalign = {"left": "top", "right": "bottom", "center": ""}
873-
874-
# TODO: matplotlib does not hide unused tick labels yet, workaround
875-
for tick in self.figure.findobj(mpl.axis.Tick):
876-
tick.label1.set_visible(tick.label1On)
877-
tick.label2.set_visible(tick.label2On)
878-
# TODO: strange, first legend label is always "None", workaround
879-
for legend in self.figure.findobj(mpl.legend.Legend):
880-
labels = legend.findobj(mpl.text.Text)
881-
labels[0].set_visible(False)
882-
# TODO: strange, legend child labels are duplicated,
883-
# find a list of unique text objects as workaround
884-
texts = self.figure.findobj(match=Text, include_self=False)
885-
texts = list(set(texts))
886-
887-
# draw text elements
888-
for text in texts:
889-
s = text.get_text()
890-
if not s or not text.get_visible():
891-
continue
892-
893-
s = common_texification(s)
894-
895-
fontsize = text.get_fontsize()
896-
angle = text.get_rotation()
897-
transform = text.get_transform()
898-
x, y = transform.transform_point(text.get_position())
899-
x = x * 1.0 / self.figure.dpi
900-
y = y * 1.0 / self.figure.dpi
901-
# TODO: positioning behavior unknown for rotated elements
902-
# right now only the alignment for 90deg rotations is correct
903-
if angle == 90.:
904-
align = rvalign[text.get_va()] + "," + rhalign[text.get_ha()]
905-
else:
906-
align = valign[text.get_va()] + "," + halign[text.get_ha()]
907-
908-
s = ur"{\fontsize{%f}{%f}\selectfont %s}" % (fontsize, fontsize*1.2, s)
909-
writeln(fh, ur"\pgftext[%s,x=%fin,y=%fin,rotate=%f]{%s}" % (align,x,y,angle,s))
910-
911887
def get_renderer(self):
912888
return RendererPgf(self.figure, None)
913889

‎lib/matplotlib/backends/backend_ps.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_ps.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
649649

650650
self._path_collection_id += 1
651651

652-
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!'):
652+
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
653653
"""
654654
draw a Text instance
655655
"""
@@ -684,7 +684,7 @@ def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!'):
684684
self._pswriter.write(ps)
685685
self.textcnt += 1
686686

687-
def draw_text(self, gc, x, y, s, prop, angle, ismath):
687+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
688688
"""
689689
draw a Text instance
690690
"""

‎lib/matplotlib/backends/backend_svg.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_svg.py
+41-11Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None):
815815
def _adjust_char_id(self, char_id):
816816
return char_id.replace(u"%20", u"_")
817817

818-
def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
818+
def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
819819
"""
820820
draw the text by converting them to paths using textpath module.
821821
@@ -940,7 +940,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
940940

941941
writer.end('g')
942942

943-
def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath):
943+
def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
944944
writer = self.writer
945945

946946
color = rgb2hex(gc.get_rgb())
@@ -953,7 +953,8 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath):
953953
if not ismath:
954954
font = self._get_font(prop)
955955
font.set_text(s, 0.0, flags=LOAD_NO_HINTING)
956-
y -= font.get_descent() / 64.0
956+
descent = font.get_descent() / 64.0
957+
y -= descent
957958

958959
fontsize = prop.get_size_in_points()
959960

@@ -967,11 +968,40 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath):
967968
style[u'font-style'] = prop.get_style().lower()
968969
attrib[u'style'] = generate_css(style)
969970

970-
attrib[u'transform'] = generate_transform([
971-
(u'translate', (x, y)),
972-
(u'rotate', (-angle,))])
971+
if angle == 0 or mtext.get_rotation_mode() == "anchor":
972+
# If text anchoring can be supported, get the original
973+
# coordinates and add alignment information.
974+
975+
# Get anchor coordinates.
976+
transform = mtext.get_transform()
977+
ax, ay = transform.transform_point(mtext.get_position())
978+
ay = self.height - ay
979+
980+
# Don't do vertical anchor alignment. Most applications do not
981+
# support 'alignment-baseline' yet. Apply the vertical layout
982+
# to the anchor point manually for now.
983+
angle_rad = angle * np.pi / 180.
984+
dir_vert = np.array([np.sin(angle_rad), np.cos(angle_rad)])
985+
y += descent # Undo inappropriate text descent handling
986+
v_offset = np.dot(dir_vert, [(x - ax), (y - ay)])
987+
ax = ax + (v_offset - descent) * dir_vert[0]
988+
ay = ay + (v_offset - descent) * dir_vert[1]
989+
990+
ha_mpl_to_svg = {'left': 'start', 'right': 'end',
991+
'center': 'middle'}
992+
style[u'text-anchor'] = ha_mpl_to_svg[mtext.get_ha()]
993+
994+
attrib[u'x'] = str(ax)
995+
attrib[u'y'] = str(ay)
996+
attrib[u'style'] = generate_css(style)
997+
attrib[u'transform'] = u"rotate(%f, %f, %f)" % (-angle, ax, ay)
998+
writer.element(u'text', s, attrib=attrib)
999+
else:
1000+
attrib[u'transform'] = generate_transform([
1001+
(u'translate', (x, y)),
1002+
(u'rotate', (-angle,))])
9731003

974-
writer.element(u'text', s, attrib=attrib)
1004+
writer.element(u'text', s, attrib=attrib)
9751005

9761006
if rcParams['svg.fonttype'] == 'svgfont':
9771007
fontset = self._fonts.setdefault(font.fname, set())
@@ -1053,10 +1083,10 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath):
10531083

10541084
writer.end(u'g')
10551085

1056-
def draw_tex(self, gc, x, y, s, prop, angle):
1086+
def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None):
10571087
self._draw_text_as_path(gc, x, y, s, prop, angle, ismath="TeX")
10581088

1059-
def draw_text(self, gc, x, y, s, prop, angle, ismath):
1089+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
10601090
clipid = self._get_clip(gc)
10611091
if clipid is not None:
10621092
# Cannot apply clip-path directly to the text, because
@@ -1065,9 +1095,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath):
10651095
u'g', attrib={u'clip-path': u'url(#%s)' % clipid})
10661096

10671097
if rcParams['svg.fonttype'] == 'path':
1068-
self._draw_text_as_path(gc, x, y, s, prop, angle, ismath)
1098+
self._draw_text_as_path(gc, x, y, s, prop, angle, ismath, mtext)
10691099
else:
1070-
self._draw_text_as_text(gc, x, y, s, prop, angle, ismath)
1100+
self._draw_text_as_text(gc, x, y, s, prop, angle, ismath, mtext)
10711101

10721102
if clipid is not None:
10731103
self.writer.end(u'g')

‎lib/matplotlib/backends/backend_template.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_template.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def draw_path(self, gc, path, transform, rgbFace=None):
104104
def draw_image(self, gc, x, y, im):
105105
pass
106106

107-
def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
107+
def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
108108
pass
109109

110110
def flipy(self):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.