Skip to content

Commit 94cd1fe

Browse files
authored
Merge branch 'master' into master
2 parents 48e1fdb + 536f272 commit 94cd1fe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1065
-442
lines changed

matplotlib2tikz/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
__copyright__ = 'Copyright (c) 2010-2017, %s <%s>' % (__author__, __email__)
66
__credits__ = []
77
__license__ = 'License :: OSI Approved :: MIT License'
8-
__version__ = '0.6.5'
8+
__version__ = '0.6.6'
99
__maintainer__ = u'Nico Schlömer'
1010
__status__ = 'Development Status :: 5 - Production/Stable'

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:

matplotlib2tikz/legend.py

Lines changed: 98 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# -*- coding: utf-8 -*-
22
#
33
import warnings
4+
5+
import numpy
6+
47
from . import color as mycol
58

69

@@ -19,52 +22,126 @@ def draw_legend(data, obj):
1922
# Get the location.
2023
# http://matplotlib.org/api/legend_api.html
2124
pad = 0.03
22-
if obj._loc == 0:
25+
loc = obj._loc
26+
if loc == 0:
2327
# best
24-
print(
25-
'Legend location "best" not yet implemented, '
26-
'choosing "upper right" instead.'
27-
)
28-
position = None
29-
anchor = None
30-
elif obj._loc == 1:
28+
# Create a renderer
29+
from matplotlib.backends import backend_agg
30+
renderer = backend_agg.RendererAgg(width=obj.figure.get_figwidth(),
31+
height=obj.figure.get_figheight(),
32+
dpi=obj.figure.dpi)
33+
34+
# Rectangles of the legend and of the axes
35+
# Lower left and upper right points
36+
x0_legend, x1_legend = obj._legend_box \
37+
.get_window_extent(renderer).get_points()
38+
x0_axes, x1_axes = obj.axes.get_window_extent(renderer).get_points()
39+
dimension_legend = x1_legend - x0_legend
40+
dimension_axes = x1_axes - x0_axes
41+
42+
# To determine the actual position of the legend, check which corner
43+
# (or center) of the legend is closest to the corresponding corner
44+
# (or center) of the axes box.
45+
# 1. Key points of the legend
46+
lower_left_legend = x0_legend
47+
lower_right_legend = numpy.array([x1_legend[0], x0_legend[1]],
48+
dtype=numpy.float_)
49+
upper_left_legend = numpy.array([x0_legend[0], x1_legend[1]],
50+
dtype=numpy.float_)
51+
upper_right_legend = x1_legend
52+
center_legend = x0_legend + dimension_legend / 2.
53+
center_left_legend = numpy.array(
54+
[x0_legend[0], x0_legend[1] + dimension_legend[1] / 2.],
55+
dtype=numpy.float_)
56+
center_right_legend = numpy.array(
57+
[x1_legend[0], x0_legend[1] + dimension_legend[1] / 2.],
58+
dtype=numpy.float_)
59+
lower_center_legend = numpy.array(
60+
[x0_legend[0] + dimension_legend[0] / 2., x0_legend[1]],
61+
dtype=numpy.float_)
62+
upper_center_legend = numpy.array(
63+
[x0_legend[0] + dimension_legend[0] / 2., x1_legend[1]],
64+
dtype=numpy.float_)
65+
66+
# 2. Key points of the axes
67+
lower_left_axes = x0_axes
68+
lower_right_axes = numpy.array([x1_axes[0], x0_axes[1]],
69+
dtype=numpy.float_)
70+
upper_left_axes = numpy.array([x0_axes[0], x1_axes[1]],
71+
dtype=numpy.float_)
72+
upper_right_axes = x1_axes
73+
center_axes = x0_axes + dimension_axes / 2.
74+
center_left_axes = numpy.array(
75+
[x0_axes[0], x0_axes[1] + dimension_axes[1] / 2.],
76+
dtype=numpy.float_)
77+
center_right_axes = numpy.array(
78+
[x1_axes[0], x0_axes[1] + dimension_axes[1] / 2.],
79+
dtype=numpy.float_)
80+
lower_center_axes = numpy.array(
81+
[x0_axes[0] + dimension_axes[0] / 2., x0_axes[1]],
82+
dtype=numpy.float_)
83+
upper_center_axes = numpy.array(
84+
[x0_axes[0] + dimension_axes[0] / 2., x1_axes[1]],
85+
dtype=numpy.float_)
86+
87+
# 3. Compute the distances between comparable points.
88+
distances = {
89+
1: upper_right_axes - upper_right_legend, # upper right
90+
2: upper_left_axes - upper_left_legend, # upper left
91+
3: lower_left_axes - lower_left_legend, # lower left
92+
4: lower_right_axes - lower_right_legend, # lower right
93+
# 5:, Not Implemented # right
94+
6: center_left_axes - center_left_legend, # center left
95+
7: center_right_axes - center_right_legend, # center right
96+
8: lower_center_axes - lower_center_legend, # lower center
97+
9: upper_center_axes - upper_center_legend, # upper center
98+
10: center_axes - center_legend # center
99+
}
100+
for k, v in distances.items():
101+
distances[k] = numpy.linalg.norm(v, ord=2)
102+
103+
# 4. Take the shortest distance between key points as the final
104+
# location
105+
loc = min(distances, key=distances.get)
106+
107+
if loc == 1:
31108
# upper right
32109
position = None
33110
anchor = None
34-
elif obj._loc == 2:
111+
elif loc == 2:
35112
# upper left
36113
position = [pad, 1.0 - pad]
37114
anchor = 'north west'
38-
elif obj._loc == 3:
115+
elif loc == 3:
39116
# lower left
40117
position = [pad, pad]
41118
anchor = 'south west'
42-
elif obj._loc == 4:
119+
elif loc == 4:
43120
# lower right
44121
position = [1.0 - pad, pad]
45122
anchor = 'south east'
46-
elif obj._loc == 5:
123+
elif loc == 5:
47124
# right
48125
position = [1.0 - pad, 0.5]
49-
anchor = 'west'
50-
elif obj._loc == 6:
126+
anchor = 'east'
127+
elif loc == 6:
51128
# center left
52129
position = [3 * pad, 0.5]
53-
anchor = 'east'
54-
elif obj._loc == 7:
130+
anchor = 'west'
131+
elif loc == 7:
55132
# center right
56133
position = [1.0 - 3 * pad, 0.5]
57-
anchor = 'west'
58-
elif obj._loc == 8:
134+
anchor = 'east'
135+
elif loc == 8:
59136
# lower center
60137
position = [0.5, 3 * pad]
61138
anchor = 'south'
62-
elif obj._loc == 9:
139+
elif loc == 9:
63140
# upper center
64141
position = [0.5, 1.0 - 3 * pad]
65142
anchor = 'north'
66143
else:
67-
assert obj._loc == 10
144+
assert loc == 10
68145
# center
69146
position = [0.5, 0.5]
70147
anchor = 'center'
@@ -100,7 +177,7 @@ def draw_legend(data, obj):
100177
if alignment != childAlignment:
101178
warnings.warn(
102179
'Varying horizontal alignments in the legend. Using default.'
103-
)
180+
)
104181
alignment = None
105182
break
106183

test/test_hashes.py renamed to test/helpers.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
# -*- coding: utf-8 -*-
22
#
33
import matplotlib2tikz
4-
import testfunctions
54

65
import os
76
import tempfile
8-
from importlib import import_module
9-
import pytest
107
import subprocess
118
from PIL import Image
129
import imagehash
1310
from matplotlib import pyplot as plt
1411

1512

16-
@pytest.mark.parametrize('name', testfunctions.__all__)
17-
def test_hash(name):
18-
test = import_module('testfunctions.' + name)
19-
# import the test
20-
fig = test.plot()
13+
def compute_phash(fig):
2114
# convert to tikz file
22-
_, tmp_base = tempfile.mkstemp(prefix=name)
15+
_, tmp_base = tempfile.mkstemp()
2316
tikz_file = tmp_base + '_tikz.tex'
2417
matplotlib2tikz.save(
2518
tikz_file,
@@ -68,7 +61,7 @@ def test_hash(name):
6861
except subprocess.CalledProcessError as e:
6962
print('Command output:')
7063
print('=' * 70)
71-
print(repr(e.output))
64+
print(e.output)
7265
print('=' * 70)
7366
if 'DISPLAY' not in os.environ:
7467
cmd = ['curl', '-sT', tikz_file, 'chunk.io']
@@ -89,14 +82,23 @@ def test_hash(name):
8982
png_file = tmp_base + '-1.png'
9083

9184
# compute the phash of the PNG
92-
phash = imagehash.phash(Image.open(png_file)).__str__()
85+
return (
86+
imagehash.phash(Image.open(png_file)).__str__(), png_file, pdf_file,
87+
tex_out, ptp_out, mpl_reference, tikz_file
88+
)
89+
90+
91+
def assert_phash(fig, reference_phash):
92+
phash, png_file, pdf_file, tex_out, ptp_out, mpl_reference, tikz_file =\
93+
compute_phash(fig)
9394

94-
if test.phash != phash:
95+
if reference_phash != phash:
9596
# Compute the Hamming distance between the two 64-bit numbers
96-
hamming_dist = bin(int(phash, 16) ^ int(test.phash, 16)).count('1')
97+
hamming_dist = \
98+
bin(int(phash, 16) ^ int(reference_phash, 16)).count('1')
9799
print('Output file: %s' % png_file)
98100
print('computed pHash: %s' % phash)
99-
print('reference pHash: %s' % test.phash)
101+
print('reference pHash: %s' % reference_phash)
100102
print(
101103
'Hamming distance: %s (out of %s)' %
102104
(hamming_dist, 4 * len(phash))
@@ -136,6 +138,6 @@ def test_hash(name):
136138
)
137139
print('Uploaded output PNG file to %s' % out.decode('utf-8'))
138140

139-
assert test.phash == phash
141+
assert reference_phash == phash
140142

141143
return
File renamed without changes.

0 commit comments

Comments
 (0)