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

Skip to content

Commit b493791

Browse files
committed
Force clipped-log when drawing log-histograms.
Just a proof of concept (but works).
1 parent 89183ba commit b493791

File tree

5 files changed

+63
-5
lines changed

5 files changed

+63
-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: 7 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

@@ -3054,7 +3055,9 @@ def extract_err(err, data):
30543055
if xlolims.any():
30553056
yo, _ = xywhere(y, right, xlolims & everymask)
30563057
lo, ro = xywhere(x, right, xlolims & everymask)
3057-
barcols.append(self.hlines(yo, lo, ro, **eb_lines_style))
3058+
ebs = self.hlines(yo, lo, ro, **eb_lines_style)
3059+
ebs._force_clip_in_log_scale = True
3060+
barcols.append(ebs)
30583061
rightup, yup = xywhere(right, y, xlolims & everymask)
30593062
if self.xaxis_inverted():
30603063
marker = mlines.CARETLEFTBASE
@@ -3093,7 +3096,9 @@ def extract_err(err, data):
30933096
if noylims.any():
30943097
xo, _ = xywhere(x, lower, noylims & everymask)
30953098
lo, uo = xywhere(lower, upper, noylims & everymask)
3096-
barcols.append(self.vlines(xo, lo, uo, **eb_lines_style))
3099+
ebs = self.vlines(xo, lo, uo, **eb_lines_style)
3100+
ebs._force_clip_in_log_scale = True
3101+
barcols.append(ebs)
30973102
if capsize > 0:
30983103
caplines.append(mlines.Line2D(xo, lo, marker='_',
30993104
**eb_cap_style))

lib/matplotlib/collections.py

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

232232
if not transform.is_affine:
233-
paths = [transform.transform_path_non_affine(path)
234-
for path in paths]
233+
with self._forcing_clip_in_log_scale():
234+
paths = [transform.transform_path_non_affine(path)
235+
for path in paths]
235236
transform = transform.get_affine()
236237
if not transOffset.is_affine:
237238
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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5319,3 +5319,20 @@ def test_barh_signature(args, kwargs, warning_count):
53195319
def test_zero_linewidth():
53205320
# Check that setting a zero linewidth doesn't error
53215321
plt.plot([0, 1], [0, 1], ls='--', lw=0)
5322+
5323+
5324+
@pytest.mark.parametrize("plotter",
5325+
[lambda ax: ax.bar([0, 1], [1, 2]),
5326+
lambda ax: ax.errorbar([0, 1], [2, 2], [1, 3])])
5327+
def test_clipped_log_zero(plotter):
5328+
fig, ax = plt.subplots()
5329+
plotter(ax)
5330+
ax.set_yscale("log")
5331+
png1 = io.BytesIO()
5332+
fig.savefig(png1, format="png")
5333+
fig, ax = plt.subplots()
5334+
plotter(ax)
5335+
ax.set_yscale("log", nonposy="clip")
5336+
png2 = io.BytesIO()
5337+
fig.savefig(png2, format="png")
5338+
assert png1.getvalue() == png2.getvalue()

0 commit comments

Comments
 (0)