Skip to content

Commit b72a6ed

Browse files
authored
Merge pull request #154 from mlq/feature/axes-tick-label-rotation
Allow rotation of axes tick labels
2 parents d759c1c + 6168809 commit b72a6ed

29 files changed

+444
-29
lines changed

matplotlib2tikz/axes.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,29 @@ def __init__(self, data, obj):
198198
assert direction == 'inout'
199199
self.axis_options.append('tick align=center')
200200

201+
# Set each rotation for every label
202+
x_tick_rotation_and_horizontal_alignment = \
203+
self.__get_label_rotation_and_horizontal_alignment(obj, data, 'x')
204+
if x_tick_rotation_and_horizontal_alignment:
205+
self.axis_options.append(x_tick_rotation_and_horizontal_alignment)
206+
207+
y_tick_rotation_and_horizontal_alignment = \
208+
self.__get_label_rotation_and_horizontal_alignment(obj, data, 'y')
209+
if y_tick_rotation_and_horizontal_alignment:
210+
self.axis_options.append(y_tick_rotation_and_horizontal_alignment)
211+
212+
# Set tick position
213+
x_tick_position_string, x_tick_position = \
214+
self.__get_tick_position(obj, data, 'x')
215+
y_tick_position_string, y_tick_position = \
216+
self.__get_tick_position(obj, data, 'y')
217+
218+
if x_tick_position == y_tick_position and x_tick_position is not None:
219+
self.axis_options.append("tick pos=%s" % x_tick_position)
220+
else:
221+
self.axis_options.append(x_tick_position_string)
222+
self.axis_options.append(y_tick_position_string)
223+
201224
# Don't use get_{x,y}gridlines for gridlines; see discussion on
202225
# <http://sourceforge.net/p/matplotlib/mailman/message/25169234/>
203226
# Coordinate of the lines are entirely meaningless, but styles
@@ -360,6 +383,113 @@ def __init__(self, data, obj):
360383

361384
return
362385

386+
def __get_label_rotation_and_horizontal_alignment(self, obj, data, axes):
387+
tick_label_text_width = None
388+
tick_label_text_width_identifier = "%s tick label text width" % axes
389+
if tick_label_text_width_identifier in data['extra axis options']:
390+
tick_label_text_width = data['extra axis options [base]']
391+
[tick_label_text_width_identifier]
392+
del data['extra axis options'][tick_label_text_width_identifier]
393+
394+
label_style = ""
395+
396+
major_tick_labels = obj.xaxis.get_majorticklabels() if axes == 'x' \
397+
else obj.yaxis.get_majorticklabels()
398+
399+
if len(major_tick_labels) == 0:
400+
return None
401+
402+
tick_labels_rotation = \
403+
[label.get_rotation() for label in major_tick_labels]
404+
tick_labels_rotation_same_value = len(set(tick_labels_rotation)) == 1
405+
406+
tick_labels_horizontal_alignment = \
407+
[label.get_horizontalalignment() for label in major_tick_labels]
408+
tick_labels_horizontal_alignment_same_value = \
409+
len(set(tick_labels_horizontal_alignment)) == 1
410+
411+
if tick_labels_rotation_same_value and \
412+
tick_labels_horizontal_alignment_same_value:
413+
values = []
414+
415+
if any(tick_labels_rotation) != 0:
416+
values.append('rotate=%d' % tick_labels_rotation[0])
417+
418+
if tick_label_text_width:
419+
values.append('align=%s' % tick_labels_horizontal_alignment[0])
420+
values.append('text width=%s' % tick_label_text_width)
421+
else:
422+
print('Horizontal alignment will be ignored as no \'%s tick '
423+
'label text width\' has been passed in the \'extra\' '
424+
'parameter' % axes)
425+
426+
if len(values) > 0:
427+
label_style = \
428+
'%sticklabel style = {%s}' % (axes, ','.join(values))
429+
else:
430+
values = []
431+
432+
if tick_labels_rotation_same_value:
433+
values.append('rotate=%d' % tick_labels_rotation[0])
434+
else:
435+
values.append('rotate={%s,0}[\\ticknum]'
436+
% ','.join(str(x) for x in tick_labels_rotation))
437+
438+
if tick_label_text_width:
439+
if tick_labels_horizontal_alignment_same_value:
440+
values.append('align=%s' %
441+
tick_labels_horizontal_alignment[0])
442+
values.append('text width=%s' % tick_label_text_width)
443+
else:
444+
for idx, x in enumerate(tick_labels_horizontal_alignment):
445+
label_style += '%s_tick_label_ha_%d/.initial = %s' \
446+
% (axes, idx, x)
447+
448+
values.append(
449+
'align=\pgfkeysvalueof{/pgfplots/'
450+
'%s_tick_label_ha_\\ticknum}' % axes)
451+
values.append('text width=%s' % tick_label_text_width)
452+
else:
453+
print('Horizontal alignment will be ignored as no \'%s tick '
454+
'label text width\' has been passed in the \'extra\' '
455+
'parameter' % axes)
456+
457+
label_style = 'every %s tick label/.style = {\n' \
458+
'%s\n' \
459+
'}' % (axes, ',\n'.join(values))
460+
461+
return label_style
462+
463+
def __get_tick_position(self, obj, data, axes):
464+
major_ticks = obj.xaxis.majorTicks if axes == 'x' else \
465+
obj.yaxis.majorTicks
466+
467+
major_ticks_bottom = [tick.tick1On for tick in major_ticks]
468+
major_ticks_top = [tick.tick2On for tick in major_ticks]
469+
470+
major_ticks_bottom_show_all = False
471+
if len(set(major_ticks_bottom)) == 1 and major_ticks_bottom[0] is True:
472+
major_ticks_bottom_show_all = True
473+
474+
major_ticks_top_show_all = False
475+
if len(set(major_ticks_top)) == 1 and major_ticks_top[0] is True:
476+
major_ticks_top_show_all = True
477+
478+
major_ticks_position = None
479+
if not major_ticks_bottom_show_all and not major_ticks_top_show_all:
480+
position_string = "%smajorticks=false" % axes
481+
elif major_ticks_bottom_show_all and major_ticks_top_show_all:
482+
major_ticks_position = 'both'
483+
elif major_ticks_bottom_show_all:
484+
major_ticks_position = 'left'
485+
elif major_ticks_top_show_all:
486+
major_ticks_position = 'right'
487+
488+
if major_ticks_position:
489+
position_string = "%stick pos=%s" % (axes, major_ticks_position)
490+
491+
return position_string, major_ticks_position
492+
363493
def get_begin_code(self):
364494
content = self.content
365495
if self.axis_options:

test/test_rotated_labels.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
import matplotlib2tikz
4+
5+
import pytest
6+
import tempfile
7+
import os
8+
from matplotlib import pyplot as plt
9+
10+
11+
def __plot():
12+
fig, ax = plt.subplots()
13+
14+
x = [1, 2, 3, 4]
15+
y = [1, 4, 9, 6]
16+
17+
plt.plot(x, y, 'ro')
18+
plt.xticks(x, rotation='horizontal')
19+
20+
return fig, ax
21+
22+
23+
@pytest.mark.parametrize(
24+
"x_alignment, y_alignment, x_tick_label_width,"
25+
"y_tick_label_width, rotation", [
26+
(None, None, "1rem", "3rem", 90),
27+
(None, "center", "1rem", "3rem", 90),
28+
("center", None, "1rem", "3rem", 90),
29+
("center", "center", None, "3rem", 90),
30+
("left", "left", None, "3rem", 90),
31+
("right", "right", None, "3rem", 90),
32+
("center", "center", "1rem", None, 90),
33+
("left", "left", "2rem", None, 90),
34+
("right", "right", "3rem", None, 90),
35+
("center", "center", "1rem", "3rem", 90),
36+
("left", "left", "2rem", "3rem", 90),
37+
("right", "right", "3rem", "3rem", 90),
38+
("left", "right", "2rem", "3rem", 90),
39+
("right", "left", "3rem", "3rem", 90),
40+
])
41+
def test_rotated_labels_parameters(x_alignment, y_alignment,
42+
x_tick_label_width, y_tick_label_width,
43+
rotation):
44+
fig, _ = __plot()
45+
46+
if x_alignment:
47+
plt.xticks(ha=x_alignment, rotation=rotation)
48+
if y_alignment:
49+
plt.yticks(ha=y_alignment, rotation=rotation)
50+
51+
# convert to tikz file
52+
_, tmp_base = tempfile.mkstemp()
53+
tikz_file = tmp_base + '_tikz.tex'
54+
55+
extra_dict = {}
56+
57+
if x_tick_label_width:
58+
extra_dict['x tick label text width'] = x_tick_label_width
59+
if y_tick_label_width:
60+
extra_dict['y tick label text width'] = y_tick_label_width
61+
62+
matplotlib2tikz.save(
63+
tikz_file,
64+
figurewidth='7.5cm',
65+
extra=extra_dict
66+
)
67+
68+
# close figure
69+
plt.close(fig)
70+
71+
# delete file
72+
os.unlink(tikz_file)
73+
74+
75+
@pytest.mark.parametrize("x_tick_label_width, y_tick_label_width", [
76+
(None, None),
77+
("1rem", None),
78+
(None, "3rem"),
79+
("2rem", "3rem"),
80+
])
81+
def test_rotated_labels_parameters_different_values(x_tick_label_width,
82+
y_tick_label_width):
83+
fig, ax = __plot()
84+
85+
plt.xticks(ha='left', rotation=90)
86+
plt.yticks(ha='left', rotation=90)
87+
ax.xaxis.get_majorticklabels()[0].set_rotation(20)
88+
ax.yaxis.get_majorticklabels()[0].set_horizontalalignment('right')
89+
90+
# convert to tikz file
91+
_, tmp_base = tempfile.mkstemp()
92+
tikz_file = tmp_base + '_tikz.tex'
93+
94+
extra_dict = {}
95+
96+
if x_tick_label_width:
97+
extra_dict['x tick label text width'] = x_tick_label_width
98+
if y_tick_label_width:
99+
extra_dict['y tick label text width'] = y_tick_label_width
100+
101+
matplotlib2tikz.save(
102+
tikz_file,
103+
figurewidth='7.5cm',
104+
extra=extra_dict
105+
)
106+
107+
# close figure
108+
plt.close(fig)
109+
110+
# delete file
111+
os.unlink(tikz_file)
112+
113+
114+
def test_rotated_labels_parameters_no_ticks():
115+
fig, ax = __plot()
116+
117+
ax.xaxis.set_ticks([])
118+
119+
plt.tick_params(axis='x',
120+
which='both',
121+
bottom='off',
122+
top='off')
123+
plt.tick_params(axis='y',
124+
which='both',
125+
left='off',
126+
right='off')
127+
128+
# convert to tikz file
129+
_, tmp_base = tempfile.mkstemp()
130+
tikz_file = tmp_base + '_tikz.tex'
131+
132+
matplotlib2tikz.save(
133+
tikz_file,
134+
figurewidth='7.5cm'
135+
)
136+
137+
# close figure
138+
plt.close(fig)
139+
140+
# delete file
141+
os.unlink(tikz_file)

test/testfunctions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
'noise2',
2626
'patches',
2727
'quadmesh',
28+
'rotated_labels',
29+
'tick_positions',
2830
'scatter',
2931
'scatter_colormap',
3032
'sharex_and_y',

test/testfunctions/annotate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
desc = 'Annotations'
44
# phash = 'ab8a78a1549654fe'
5-
phash = 'ab8a71a1569654de'
5+
phash = 'ab8a79a1549654be'
66

77

88
def plot():

test/testfunctions/barchart.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
88
"""
99
desc = 'Bar Chart'
10-
phash = '5f09a9e6b3728742'
10+
phash = '5f09a9e6b172874a'
11+
1112

1213
def plot():
1314
import matplotlib.pyplot as plt

test/testfunctions/barchart_errorbars.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
77
"""
88
desc = 'Bar Chart With Errorbars'
9-
phash = '5f09a9e4b37287c2'
9+
phash = '5f09a9e6b1728746'
10+
1011

1112
def plot():
1213
import matplotlib.pyplot as plt

test/testfunctions/barchart_legend.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
patches that should not be plotted in PGFPlots (e.g. axis, legend)
77
88
This also tests legends on barcharts. Which are difficult because
9-
in PGFPlots, they have no \\addplot, and thus legend must be
9+
in PGFPlots, they have no \\addplot, and thus legend must be
1010
manually added.
1111
1212
"""
1313
desc = 'Bar Chart'
14-
phash = '5f09a9e633728dc4'
14+
phash = '5f19a966937285cc'
15+
1516

1617
def plot():
1718
import matplotlib.pyplot as plt

test/testfunctions/basic_sin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
desc = 'Simple $\sin$ plot with some labels'
44
# phash = '5f34e1ce21c3e5c1'
5-
phash = '1f36e1ce21c1e7c1'
5+
phash = '1f36e5c621c1e7c1'
66

77

88
def plot():

test/testfunctions/boxplot.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
44
This test plots a box plot with three data series. The causes an empty Line2D
55
to be plotted. Without care, this can turn into an empty table in PGFPlot
6-
which crashes latex (due to it treating an empty table as a table with
6+
which crashes latex (due to it treating an empty table as a table with
77
external data in the file '' or '.tex')
88
See: https://github.com/nschloe/matplotlib2tikz/pull/134
99
"""
1010
desc = 'BoxPlot'
11-
phash = '6bc6f6b95e0000fe'
11+
phash = '6be3e6b95e8000de'
12+
1213

1314
def plot():
1415
import matplotlib.pyplot as plt

test/testfunctions/dual_axis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
desc = 'Dual axis plot'
44
# phash = '43a35c4b76a94eb8'
5-
phash = '5bab54492628de7c'
5+
phash = '5382544c16bdde75'
66

77

88
def plot():

0 commit comments

Comments
 (0)