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

Skip to content

Commit 957a5a1

Browse files
committed
Force clipped-log for hist/errorbar/fill_between.
1 parent a5c0164 commit 957a5a1

File tree

5 files changed

+66
-5
lines changed

5 files changed

+66
-5
lines changed

lib/matplotlib/artist.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import six
55

66
from collections import OrderedDict, namedtuple
7+
import contextlib
78
from functools import wraps
89
import inspect
910
import re
@@ -120,6 +121,22 @@ def __init__(self):
120121
self._path_effects = rcParams['path.effects']
121122
self._sticky_edges = _XYPair([], [])
122123

124+
# When plotting in log-scale, force the use of clip mode instead of
125+
# mask. The typical (internal) use case is log-scaled bar plots and
126+
# error bars. Ideally we'd want BarContainers / ErrorbarContainers
127+
# to have their own show() method which takes care of the patching,
128+
# but right now Containers are not taken into account during
129+
# the draw; instead their components (in the case of bar plots,
130+
# these are Rectangle patches; in the case of ErrorbarContainers,
131+
# LineCollections) are drawn individually, so tracking the force_clip
132+
# state must be done by the component artists. Note that handling of
133+
# _force_clip_in_log_scale must be done by the individual artists'
134+
# draw implementation; right now only Patches and Collections support
135+
# it. The `_forcing_clip_in_log_scale` decorator may be helpful to
136+
# implement such support, it should typically be applied around a call
137+
# to `transform_path_non_affine`.
138+
self._force_clip_in_log_scale = False
139+
123140
def __getstate__(self):
124141
d = self.__dict__.copy()
125142
# remove the unpicklable remove method, this will get re-added on load
@@ -779,6 +796,21 @@ def draw(self, renderer, *args, **kwargs):
779796
return
780797
self.stale = False
781798

799+
@contextlib.contextmanager
800+
def _forcing_clip_in_log_scale(self):
801+
# See _force_clip_in_log_scale for explanation.
802+
fvs = {}
803+
if self._force_clip_in_log_scale and self.axes:
804+
for axis in self.axes._get_axis_list():
805+
if axis.get_scale() == "log":
806+
fvs[axis] = axis._scale._transform._fill_value
807+
axis._scale._transform._fill_value = 1e-300
808+
try:
809+
yield
810+
finally:
811+
for axis, fv in fvs.items():
812+
axis._scale._transform._fill_value = fv
813+
782814
def set_alpha(self, alpha):
783815
"""
784816
Set the alpha value used for blending - not supported on

lib/matplotlib/axes/_axes.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,7 @@ def bar(self, *args, **kwargs):
21582158
r.sticky_edges.y.append(b)
21592159
elif orientation == 'horizontal':
21602160
r.sticky_edges.x.append(l)
2161+
r._force_clip_in_log_scale = True
21612162
self.add_patch(r)
21622163
patches.append(r)
21632164

@@ -3053,7 +3054,9 @@ def extract_err(err, data):
30533054
if xlolims.any():
30543055
yo, _ = xywhere(y, right, xlolims & everymask)
30553056
lo, ro = xywhere(x, right, xlolims & everymask)
3056-
barcols.append(self.hlines(yo, lo, ro, **eb_lines_style))
3057+
ebs = self.hlines(yo, lo, ro, **eb_lines_style)
3058+
ebs._force_clip_in_log_scale = True
3059+
barcols.append(ebs)
30573060
rightup, yup = xywhere(right, y, xlolims & everymask)
30583061
if self.xaxis_inverted():
30593062
marker = mlines.CARETLEFTBASE
@@ -3092,7 +3095,9 @@ def extract_err(err, data):
30923095
if noylims.any():
30933096
xo, _ = xywhere(x, lower, noylims & everymask)
30943097
lo, uo = xywhere(lower, upper, noylims & everymask)
3095-
barcols.append(self.vlines(xo, lo, uo, **eb_lines_style))
3098+
ebs = self.vlines(xo, lo, uo, **eb_lines_style)
3099+
ebs._force_clip_in_log_scale = True
3100+
barcols.append(ebs)
30963101
if capsize > 0:
30973102
caplines.append(mlines.Line2D(xo, lo, marker='_',
30983103
**eb_cap_style))
@@ -4905,6 +4910,7 @@ def get_interp_point(ind):
49054910
polys.append(X)
49064911

49074912
collection = mcoll.PolyCollection(polys, **kwargs)
4913+
collection._force_clip_in_log_scale = True
49084914

49094915
# now update the datalim and autoscale
49104916
XY1 = np.array([x[where], y1[where]]).T
@@ -5057,6 +5063,7 @@ def get_interp_point(ind):
50575063
polys.append(Y)
50585064

50595065
collection = mcoll.PolyCollection(polys, **kwargs)
5066+
collection._force_clip_in_log_scale = True
50605067

50615068
# now update the datalim and autoscale
50625069
X1Y = np.array([x1[where], y[where]]).T

lib/matplotlib/collections.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,9 @@ def _prepare_points(self):
232232
offsets = np.column_stack([xs, ys])
233233

234234
if not transform.is_affine:
235-
paths = [transform.transform_path_non_affine(path)
236-
for path in paths]
235+
with self._forcing_clip_in_log_scale():
236+
paths = [transform.transform_path_non_affine(path)
237+
for path in paths]
237238
transform = transform.get_affine()
238239
if not transOffset.is_affine:
239240
offsets = transOffset.transform_non_affine(offsets)

lib/matplotlib/patches.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,10 @@ def draw(self, renderer):
564564

565565
path = self.get_path()
566566
transform = self.get_transform()
567-
tpath = transform.transform_path_non_affine(path)
567+
568+
with self._forcing_clip_in_log_scale():
569+
tpath = transform.transform_path_non_affine(path)
570+
568571
affine = transform.get_affine()
569572

570573
if self.get_path_effects():

lib/matplotlib/tests/test_axes.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5412,3 +5412,21 @@ def test_patch_deprecations():
54125412
assert fig.patch == fig.figurePatch
54135413

54145414
assert len(w) == 2
5415+
5416+
5417+
@pytest.mark.parametrize("plotter",
5418+
[lambda ax: ax.bar([0, 1], [1, 2]),
5419+
lambda ax: ax.errorbar([0, 1], [2, 2], [1, 3]),
5420+
lambda ax: ax.fill_between([0, 1], [1, 2], [1, 0])])
5421+
def test_clipped_log_zero(plotter):
5422+
fig, ax = plt.subplots()
5423+
plotter(ax)
5424+
ax.set_yscale("log")
5425+
png1 = io.BytesIO()
5426+
fig.savefig(png1, format="png")
5427+
fig, ax = plt.subplots()
5428+
plotter(ax)
5429+
ax.set_yscale("log", nonposy="clip")
5430+
png2 = io.BytesIO()
5431+
fig.savefig(png2, format="png")
5432+
assert png1.getvalue() == png2.getvalue()

0 commit comments

Comments
 (0)