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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy