Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cb5893d

Browse files
author
klaus
committed
deterministic svg output by sorting dict items
1 parent 2181f7e commit cb5893d

File tree

8 files changed

+88
-12
lines changed

8 files changed

+88
-12
lines changed

doc/api/backend_svg_api.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
:mod:`matplotlib.backends.backend_svg`
3+
======================================
4+
5+
.. automodule:: matplotlib.backends.backend_svg
6+
:members:
7+
:show-inheritance:

doc/api/index_backend_api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ backends
1212
backend_qt5agg_api.rst
1313
backend_wxagg_api.rst
1414
backend_pdf_api.rst
15+
backend_svg_api.rst
1516
.. backend_webagg.rst
1617
dviread.rst
1718
type1font.rst

lib/matplotlib/axes/_base.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from matplotlib import cbook
1919
from matplotlib.cbook import (_check_1d, _string_to_bool, iterable,
20-
index_of, get_label)
20+
index_of, get_label, sorted_itervalues)
2121
from matplotlib import docstring
2222
import matplotlib.colors as mcolors
2323
import matplotlib.lines as mlines
@@ -33,7 +33,6 @@
3333
import matplotlib.image as mimage
3434
from matplotlib.offsetbox import OffsetBox
3535
from matplotlib.artist import allow_rasterization
36-
from matplotlib.cbook import iterable, index_of
3736
from matplotlib.rcsetup import cycler
3837

3938
rcParams = matplotlib.rcParams
@@ -3687,7 +3686,7 @@ def get_children(self):
36873686
children.extend(self.lines)
36883687
children.extend(self.texts)
36893688
children.extend(self.artists)
3690-
children.extend(six.itervalues(self.spines))
3689+
children.extend(sorted_itervalues(self.spines))
36913690
children.append(self.xaxis)
36923691
children.append(self.yaxis)
36933692
children.append(self.title)

lib/matplotlib/backends/backend_svg.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
1818
FigureManagerBase, FigureCanvasBase
1919
from matplotlib.backends.backend_mixed import MixedModeRenderer
20-
from matplotlib.cbook import is_string_like, is_writable_file_like, maxdict
20+
from matplotlib.cbook import (is_string_like, is_writable_file_like, maxdict,
21+
sorted_iteritems, sorted_itervalues)
2122
from matplotlib.colors import rgb2hex
2223
from matplotlib.figure import Figure
2324
from matplotlib.font_manager import findfont, FontProperties, get_font
@@ -358,7 +359,7 @@ def _write_hatches(self):
358359
HATCH_SIZE = 72
359360
writer = self.writer
360361
writer.start('defs')
361-
for ((path, face, stroke), oid) in six.itervalues(self._hatchd):
362+
for ((path, face, stroke), oid) in sorted_itervalues(self._hatchd):
362363
writer.start(
363364
'pattern',
364365
id=oid,
@@ -469,7 +470,7 @@ def _write_clips(self):
469470
return
470471
writer = self.writer
471472
writer.start('defs')
472-
for clip, oid in six.itervalues(self._clipd):
473+
for clip, oid in sorted_itervalues(self._clipd):
473474
writer.start('clipPath', id=oid)
474475
if len(clip) == 2:
475476
clippath, clippath_trans = clip
@@ -488,7 +489,7 @@ def _write_svgfonts(self):
488489

489490
writer = self.writer
490491
writer.start('defs')
491-
for font_fname, chars in six.iteritems(self._fonts):
492+
for font_fname, chars in sorted_iteritems(self._fonts):
492493
font = get_font(font_fname)
493494
font.set_size(72, 72)
494495
sfnt = font.get_sfnt()
@@ -829,7 +830,7 @@ def draw_image(self, gc, x, y, im, dx=None, dy=None, transform=None):
829830
if rcParams['svg.image_inline']:
830831
bytesio = io.BytesIO()
831832
_png.write_png(np.array(im)[::-1], bytesio)
832-
oid = oid or self._make_id('image', bytesio)
833+
oid = oid or self._make_id('image', bytesio.getvalue())
833834
attrib['xlink:href'] = (
834835
"data:image/png;base64,\n" +
835836
base64.b64encode(bytesio.getvalue()).decode('ascii'))
@@ -917,7 +918,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
917918

918919
if glyph_map_new:
919920
writer.start('defs')
920-
for char_id, glyph_path in six.iteritems(glyph_map_new):
921+
for char_id, glyph_path in sorted_iteritems(glyph_map_new):
921922
path = Path(*glyph_path)
922923
path_data = self._convert_path(path, simplify=False)
923924
writer.element('path', id=char_id, d=path_data)
@@ -960,7 +961,7 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath, mtext=None):
960961
# used.
961962
if glyph_map_new:
962963
writer.start('defs')
963-
for char_id, glyph_path in six.iteritems(glyph_map_new):
964+
for char_id, glyph_path in sorted_iteritems(glyph_map_new):
964965
char_id = self._adjust_char_id(char_id)
965966
# Some characters are blank
966967
if not len(glyph_path[0]):
@@ -1104,7 +1105,7 @@ def _draw_text_as_text(self, gc, x, y, s, prop, angle, ismath, mtext=None):
11041105
fontset = self._fonts.setdefault(font.fname, set())
11051106
fontset.add(thetext)
11061107

1107-
for style, chars in six.iteritems(spans):
1108+
for style, chars in sorted_iteritems(spans):
11081109
chars.sort()
11091110

11101111
same_y = True

lib/matplotlib/cbook.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2491,3 +2491,17 @@ def __exit__(self, exc_type, exc_value, traceback):
24912491
os.rmdir(path)
24922492
except OSError:
24932493
pass
2494+
2495+
2496+
def sorted_iteritems(a):
2497+
"""
2498+
Iterate over the items of a dictionary in an order defined by the keys
2499+
"""
2500+
return ((k,v) for k,v in sorted(six.iteritems(a)))
2501+
2502+
2503+
def sorted_itervalues(a):
2504+
"""
2505+
Iterate over the values of a dictionary in an order defined by the keys
2506+
"""
2507+
return (v for k,v in sorted_iteritems(a))

lib/matplotlib/rcsetup.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ def validate_float_or_None(s):
166166
raise ValueError('Could not convert "%s" to float or None' % s)
167167

168168

169+
def validate_string_or_None(s):
170+
"""convert s to string or raise"""
171+
if s is None:
172+
return None
173+
try:
174+
return six.text_type(s)
175+
except ValueError:
176+
raise ValueError('Could not convert "%s" to string' % s)
177+
178+
169179
def validate_dpi(s):
170180
"""confirm s is string 'figure' or convert s to float or raise"""
171181
if s == 'figure':
@@ -1165,7 +1175,7 @@ def validate_hist_bins(s):
11651175
# True to save all characters as paths in the SVG
11661176
'svg.embed_char_paths': [True, deprecate_svg_embed_char_paths],
11671177
'svg.fonttype': ['path', validate_svg_fonttype],
1168-
'svg.hashsalt': [None, validate_any],
1178+
'svg.hashsalt': [None, validate_string_or_None],
11691179

11701180
# set this when you want to generate hardcopy docstring
11711181
'docstring.hardcopy': [False, validate_bool],

lib/matplotlib/tests/test_backend_svg.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,48 @@ def test_bold_font_output_with_none_fonttype():
119119
ax.set_title('bold-title', fontweight='bold')
120120

121121

122+
def _test_determinism(filename):
123+
# This function is mostly copy&paste from "def test_visibility"
124+
# To require no GUI, we use Figure and FigureCanvasSVG
125+
# instead of plt.figure and fig.savefig
126+
from matplotlib.figure import Figure
127+
from matplotlib.backends.backend_svg import FigureCanvasSVG
128+
from matplotlib import rc
129+
rc('svg', hashsalt='asdf')
130+
131+
fig = Figure()
132+
ax = fig.add_subplot(111)
133+
134+
x = np.linspace(0, 4 * np.pi, 50)
135+
y = np.sin(x)
136+
yerr = np.ones_like(y)
137+
138+
a, b, c = ax.errorbar(x, y, yerr=yerr, fmt='ko')
139+
for artist in b:
140+
artist.set_visible(False)
141+
142+
FigureCanvasSVG(fig).print_svg(filename)
143+
144+
145+
@cleanup
146+
def test_determinism():
147+
import os
148+
import sys
149+
from subprocess import check_call
150+
from nose.tools import assert_equal
151+
plots = []
152+
for i in range(3):
153+
check_call([sys.executable, '-R', '-c',
154+
'from matplotlib.tests.test_backend_svg '
155+
'import _test_determinism;'
156+
'_test_determinism("determinism.svg")'])
157+
with open('determinism.svg', 'rb') as fd:
158+
plots.append(fd.read())
159+
os.unlink('determinism.svg')
160+
for p in plots[1:]:
161+
assert_equal(p, plots[0])
162+
163+
122164
if __name__ == '__main__':
123165
import nose
124166
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

matplotlibrc.template

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ backend : %(backend)s
505505
# 'path': Embed characters as paths -- supported by most SVG renderers
506506
# 'svgfont': Embed characters as SVG fonts -- supported only by Chrome,
507507
# Opera and Safari
508+
#svg.hashsalt : None # if not None, use this string as hash salt
509+
# instead of uuid4
508510

509511
# docstring params
510512
#docstring.hardcopy = False # set this when you want to generate hardcopy docstring

0 commit comments

Comments
 (0)