Skip to content

Commit e086a5d

Browse files
committed
Merge remote-tracking branch 'matplotlib/v2.x'
Conflicts: examples/showcase/anatomy.py White space conflict
2 parents 4303c3c + 39c7d1e commit e086a5d

File tree

19 files changed

+126
-88
lines changed

19 files changed

+126
-88
lines changed

doc/faq/anatomy.png

-2.3 KB
Loading

doc/users/dflt_style_changes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,11 @@ The behavior of the PS and Agg backends was DPI dependent, thus::
625625

626626
There is no API level control of the hatch color or linewidth.
627627

628+
Hatching patterns are now rendered at a consistent density, regardless of DPI.
629+
Formerly, high DPI figures would be more dense than the default, and low DPI
630+
figures would be less dense. This old behavior cannot be directly restored,
631+
but the density may be increased by repeating the hatch specifier.
632+
628633

629634
.. _default_changes_font:
630635

examples/showcase/anatomy.py

+24-23
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import numpy as np
44
import matplotlib.pyplot as plt
5-
from matplotlib.ticker import MultipleLocator, FuncFormatter
5+
from matplotlib.ticker import AutoMinorLocator, MultipleLocator, FuncFormatter
66

77
np.random.seed(19680801)
88

@@ -11,7 +11,7 @@
1111
Y2 = 1+np.cos(1+X/0.75)/2
1212
Y3 = np.random.uniform(Y1, Y2, len(X))
1313

14-
fig = plt.figure(figsize=(8, 8), facecolor="w")
14+
fig = plt.figure(figsize=(8, 8))
1515
ax = fig.add_subplot(1, 1, 1, aspect=1)
1616

1717

@@ -21,9 +21,9 @@ def minor_tick(x, pos):
2121
return "%.2f" % x
2222

2323
ax.xaxis.set_major_locator(MultipleLocator(1.000))
24-
ax.xaxis.set_minor_locator(MultipleLocator(0.250))
24+
ax.xaxis.set_minor_locator(AutoMinorLocator(4))
2525
ax.yaxis.set_major_locator(MultipleLocator(1.000))
26-
ax.yaxis.set_minor_locator(MultipleLocator(0.250))
26+
ax.yaxis.set_minor_locator(AutoMinorLocator(4))
2727
ax.xaxis.set_minor_formatter(FuncFormatter(minor_tick))
2828

2929
ax.set_xlim(0, 4)
@@ -38,13 +38,14 @@ def minor_tick(x, pos):
3838

3939
ax.plot(X, Y1, c=(0.25, 0.25, 1.00), lw=2, label="Blue signal", zorder=10)
4040
ax.plot(X, Y2, c=(1.00, 0.25, 0.25), lw=2, label="Red signal")
41-
ax.scatter(X, Y3, c='w')
41+
ax.plot(X, Y3, linewidth=0,
42+
marker='o', markerfacecolor='w', markeredgecolor='k')
4243

43-
ax.set_title("Anatomy of a figure", fontsize=20)
44+
ax.set_title("Anatomy of a figure", fontsize=20, verticalalignment='bottom')
4445
ax.set_xlabel("X axis label")
4546
ax.set_ylabel("Y axis label")
4647

47-
ax.legend(frameon=False)
48+
ax.legend()
4849

4950

5051
def circle(x, y, radius=0.15):
@@ -62,32 +63,32 @@ def text(x, y, text):
6263

6364

6465
# Minor tick
65-
circle(0.50, -.05)
66-
text(0.50, -0.25, "Minor tick label")
66+
circle(0.50, -0.10)
67+
text(0.50, -0.32, "Minor tick label")
6768

6869
# Major tick
69-
circle(4.00, 2.00)
70-
text(4.00, 1.80, "Major tick")
70+
circle(-0.03, 4.00)
71+
text(0.03, 3.80, "Major tick")
7172

7273
# Minor tick
73-
circle(0.25, 4.00)
74-
text(0.25, 3.80, "Minor tick")
74+
circle(0.00, 3.50)
75+
text(0.00, 3.30, "Minor tick")
7576

7677
# Major tick label
77-
circle(-0.05, 3.00)
78-
text(-0.05, 2.80, "Major tick label")
78+
circle(-0.15, 3.00)
79+
text(-0.15, 2.80, "Major tick label")
7980

8081
# X Label
81-
circle(1.80, -0.22)
82-
text(1.80, -0.4, "X axis label")
82+
circle(1.80, -0.27)
83+
text(1.80, -0.45, "X axis label")
8384

8485
# Y Label
85-
circle(-0.20, 1.80)
86-
text(-0.20, 1.6, "Y axis label")
86+
circle(-0.27, 1.80)
87+
text(-0.27, 1.6, "Y axis label")
8788

8889
# Title
89-
circle(1.60, 4.10)
90-
text(1.60, 3.9, "Title")
90+
circle(1.60, 4.13)
91+
text(1.60, 3.93, "Title")
9192

9293
# Blue plot
9394
circle(1.75, 2.80)
@@ -106,8 +107,8 @@ def text(x, y, text):
106107
text(3.00, 2.80, "Grid")
107108

108109
# Legend
109-
circle(3.70, 3.75)
110-
text(3.70, 3.55, "Legend")
110+
circle(3.70, 3.80)
111+
text(3.70, 3.60, "Legend")
111112

112113
# Axes
113114
circle(0.5, 0.5)

lib/matplotlib/axes/_base.py

+62-46
Original file line numberDiff line numberDiff line change
@@ -2173,6 +2173,10 @@ def autoscale(self, enable=True, axis='both', tight=None):
21732173
if axis in ['y', 'both']:
21742174
self._autoscaleYon = bool(enable)
21752175
scaley = self._autoscaleYon
2176+
if tight and scalex:
2177+
self._xmargin = 0
2178+
if tight and scaley:
2179+
self._ymargin = 0
21762180
self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley)
21772181

21782182
def autoscale_view(self, tight=None, scalex=True, scaley=True):
@@ -2182,6 +2186,14 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
21822186
setting *scaley* to *False*. The autoscaling preserves any
21832187
axis direction reversal that has already been done.
21842188
2189+
If *tight* is *False*, the axis major locator will be used
2190+
to expand the view limits if rcParams['axes.autolimit_mode']
2191+
is 'round_numbers'. Note that any margins that are in effect
2192+
will be applied first, regardless of whether *tight* is
2193+
*True* or *False*. Specifying *tight* as *True* or *False*
2194+
saves the setting as a private attribute of the Axes; specifying
2195+
it as *None* (the default) applies the previously saved value.
2196+
21852197
The data limits are not updated automatically when artist data are
21862198
changed after the artist has been added to an Axes instance. In that
21872199
case, use :meth:`matplotlib.axes.Axes.relim` prior to calling
@@ -2234,52 +2246,56 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
22342246
def handle_single_axis(scale, autoscaleon, shared_axes, interval,
22352247
minpos, axis, margin, do_lower_margin,
22362248
do_upper_margin, set_bound):
2237-
if scale and autoscaleon:
2238-
shared = shared_axes.get_siblings(self)
2239-
dl = [ax.dataLim for ax in shared]
2240-
# ignore non-finite data limits if good limits exist
2241-
finite_dl = [d for d in dl if np.isfinite(d).all()]
2242-
if len(finite_dl):
2243-
dl = finite_dl
2244-
2245-
bb = mtransforms.BboxBase.union(dl)
2246-
x0, x1 = getattr(bb, interval)
2247-
locator = axis.get_major_locator()
2248-
try:
2249-
# e.g., DateLocator has its own nonsingular()
2250-
x0, x1 = locator.nonsingular(x0, x1)
2251-
except AttributeError:
2252-
# Default nonsingular for, e.g., MaxNLocator
2253-
x0, x1 = mtransforms.nonsingular(
2254-
x0, x1, increasing=False, expander=0.05)
2255-
2256-
if margin > 0 and (do_lower_margin or do_upper_margin):
2257-
if axis.get_scale() == 'linear':
2258-
delta = (x1 - x0) * margin
2259-
if do_lower_margin:
2260-
x0 -= delta
2261-
if do_upper_margin:
2262-
x1 += delta
2263-
else:
2264-
# If we have a non-linear scale, we need to
2265-
# add the margin in figure space and then
2266-
# transform back
2267-
minpos = getattr(bb, minpos)
2268-
transform = axis.get_transform()
2269-
inverse_trans = transform.inverted()
2270-
x0, x1 = axis._scale.limit_range_for_scale(
2271-
x0, x1, minpos)
2272-
x0t, x1t = transform.transform([x0, x1])
2273-
delta = (x1t - x0t) * margin
2274-
if do_lower_margin:
2275-
x0t -= delta
2276-
if do_upper_margin:
2277-
x1t += delta
2278-
x0, x1 = inverse_trans.transform([x0t, x1t])
2279-
2280-
if not _tight:
2281-
x0, x1 = locator.view_limits(x0, x1)
2282-
set_bound(x0, x1)
2249+
2250+
if not (scale and autoscaleon):
2251+
return # nothing to do...
2252+
2253+
shared = shared_axes.get_siblings(self)
2254+
dl = [ax.dataLim for ax in shared]
2255+
# ignore non-finite data limits if good limits exist
2256+
finite_dl = [d for d in dl if np.isfinite(d).all()]
2257+
if len(finite_dl):
2258+
dl = finite_dl
2259+
2260+
bb = mtransforms.BboxBase.union(dl)
2261+
x0, x1 = getattr(bb, interval)
2262+
locator = axis.get_major_locator()
2263+
try:
2264+
# e.g., DateLocator has its own nonsingular()
2265+
x0, x1 = locator.nonsingular(x0, x1)
2266+
except AttributeError:
2267+
# Default nonsingular for, e.g., MaxNLocator
2268+
x0, x1 = mtransforms.nonsingular(
2269+
x0, x1, increasing=False, expander=0.05)
2270+
2271+
if margin > 0 and (do_lower_margin or do_upper_margin):
2272+
if axis.get_scale() == 'linear':
2273+
delta = (x1 - x0) * margin
2274+
if do_lower_margin:
2275+
x0 -= delta
2276+
if do_upper_margin:
2277+
x1 += delta
2278+
else:
2279+
# If we have a non-linear scale, we need to
2280+
# add the margin in figure space and then
2281+
# transform back
2282+
minpos = getattr(bb, minpos)
2283+
transform = axis.get_transform()
2284+
inverse_trans = transform.inverted()
2285+
x0, x1 = axis._scale.limit_range_for_scale(
2286+
x0, x1, minpos)
2287+
x0t, x1t = transform.transform([x0, x1])
2288+
delta = (x1t - x0t) * margin
2289+
if do_lower_margin:
2290+
x0t -= delta
2291+
if do_upper_margin:
2292+
x1t += delta
2293+
x0, x1 = inverse_trans.transform([x0t, x1t])
2294+
2295+
if not _tight:
2296+
x0, x1 = locator.view_limits(x0, x1)
2297+
set_bound(x0, x1)
2298+
# End of definition of internal function 'handle_single_axis'.
22832299

22842300
handle_single_axis(
22852301
scalex, self._autoscaleXon, self._shared_x_axes,

lib/matplotlib/backends/backend_pdf.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -1152,12 +1152,12 @@ def alphaState(self, alpha):
11521152
def hatchPattern(self, hatch_style):
11531153
# The colors may come in as numpy arrays, which aren't hashable
11541154
if hatch_style is not None:
1155-
face, edge, hatch = hatch_style
1156-
if face is not None:
1157-
face = tuple(face)
1155+
edge, face, hatch = hatch_style
11581156
if edge is not None:
11591157
edge = tuple(edge)
1160-
hatch_style = (face, edge, hatch)
1158+
if face is not None:
1159+
face = tuple(face)
1160+
hatch_style = (edge, face, hatch)
11611161

11621162
pattern = self.hatchPatterns.get(hatch_style, None)
11631163
if pattern is not None:
@@ -1182,7 +1182,9 @@ def writeHatches(self):
11821182
'PatternType': 1, 'PaintType': 1, 'TilingType': 1,
11831183
'BBox': [0, 0, sidelen, sidelen],
11841184
'XStep': sidelen, 'YStep': sidelen,
1185-
'Resources': res})
1185+
'Resources': res,
1186+
# Change origin to match Agg at top-left.
1187+
'Matrix': [1, 0, 0, 1, 0, self.height * 72]})
11861188

11871189
stroke_rgb, fill_rgb, path = hatch_style
11881190
self.output(stroke_rgb[0], stroke_rgb[1], stroke_rgb[2],
@@ -1195,13 +1197,11 @@ def writeHatches(self):
11951197

11961198
self.output(rcParams['hatch.linewidth'], Op.setlinewidth)
11971199

1198-
# TODO: We could make this dpi-dependent, but that would be
1199-
# an API change
12001200
self.output(*self.pathOperations(
12011201
Path.hatch(path),
12021202
Affine2D().scale(sidelen),
12031203
simplify=False))
1204-
self.output(Op.stroke)
1204+
self.output(Op.fill_stroke)
12051205

12061206
self.endStream()
12071207
self.writeObject(self.hatchObject, hatchDict)

lib/matplotlib/backends/backend_ps.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ def create_hatch(self, hatch):
298298
return self._hatches[hatch]
299299
name = 'H%d' % len(self._hatches)
300300
linewidth = rcParams['hatch.linewidth']
301+
pageheight = self.height * 72
301302
self._pswriter.write("""\
302303
<< /PatternType 1
303304
/PaintType 2
@@ -311,13 +312,15 @@ def create_hatch(self, hatch):
311312
%(linewidth)f setlinewidth
312313
""" % locals())
313314
self._pswriter.write(
314-
self._convert_path(Path.hatch(hatch), Affine2D().scale(72.0),
315+
self._convert_path(Path.hatch(hatch), Affine2D().scale(sidelen),
315316
simplify=False))
316317
self._pswriter.write("""\
317-
stroke
318+
fill
319+
stroke
318320
} bind
319321
>>
320322
matrix
323+
0.0 %(pageheight)f translate
321324
makepattern
322325
/%(name)s exch def
323326
""" % locals())
@@ -854,6 +857,7 @@ def _draw_ps(self, ps, gc, rgbFace, fill=True, stroke=True, command=None):
854857
stroke = stroke and mightstroke
855858
fill = (fill and rgbFace is not None and
856859
(len(rgbFace) <= 3 or rgbFace[3] != 0.0))
860+
hatch = gc.get_hatch()
857861

858862
if mightstroke:
859863
self.set_linewidth(gc.get_linewidth())
@@ -879,19 +883,18 @@ def _draw_ps(self, ps, gc, rgbFace, fill=True, stroke=True, command=None):
879883
write("\n")
880884

881885
if fill:
882-
if stroke:
886+
if stroke or hatch:
883887
write("gsave\n")
884888
self.set_color(store=0, *rgbFace[:3])
885889
write("fill\n")
886-
if stroke:
890+
if stroke or hatch:
887891
write("grestore\n")
888892

889-
hatch = gc.get_hatch()
890893
if hatch:
891894
hatch_name = self.create_hatch(hatch)
892895
write("gsave\n")
893-
write("[/Pattern [/DeviceRGB]] setcolorspace %f %f %f " % gc.get_hatch_color()[:3])
894-
write("%s setcolor fill grestore\n" % hatch_name)
896+
write("%f %f %f " % gc.get_hatch_color()[:3])
897+
write("%s setpattern fill grestore\n" % hatch_name)
895898

896899
if stroke:
897900
write("stroke\n")
Loading
Binary file not shown.
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

lib/matplotlib/tests/test_axes.py

+10
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ def test_autoscale_tiny_range():
165165
ax[i].plot([0, 1], [1, 1 + y1])
166166

167167

168+
@cleanup(style='default')
169+
def test_autoscale_tight():
170+
fig, ax = plt.subplots(1, 1)
171+
ax.plot([1, 2, 3, 4])
172+
ax.autoscale(enable=True, axis='x', tight=False)
173+
ax.autoscale(enable=True, axis='y', tight=True)
174+
assert_allclose(ax.get_xlim(), (-0.15, 3.15))
175+
assert_allclose(ax.get_ylim(), (1.0, 4.0))
176+
177+
168178
@image_comparison(baseline_images=['offset_points'],
169179
remove_text=True)
170180
def test_basic_annotate():

src/_backend_agg.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,14 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi)
5858
rendererBase.clear(_fill_color);
5959
rendererAA.attach(rendererBase);
6060
rendererBin.attach(rendererBase);
61-
hatchRenderingBuffer.attach(hatchBuffer, HATCH_SIZE, HATCH_SIZE, HATCH_SIZE * 4);
61+
hatch_size = int(dpi);
62+
hatchBuffer = new agg::int8u[hatch_size * hatch_size * 4];
63+
hatchRenderingBuffer.attach(hatchBuffer, hatch_size, hatch_size, hatch_size * 4);
6264
}
6365

6466
RendererAgg::~RendererAgg()
6567
{
68+
delete[] hatchBuffer;
6669
delete[] alphaBuffer;
6770
delete[] pixBuffer;
6871
}

0 commit comments

Comments
 (0)