GeezerVison is showing github.com — Merge pull request #5785 from anntzer/better-offsettext-choice · matplotlib/matplotlib@beb08c8
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +6Lines changed: 6 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Improved offset text choice | | 2 | +--------------------------- | | 3 | +The default offset-text choice was changed to only use significant digits that | | 4 | +are common to all ticks (e.g. 1231..1239 -> 1230, instead of 1231), except when | | 5 | +they straddle a relatively large multiple of a power of ten, in which case that | | 6 | +multiple is chosen (e.g. 1999..2001->2000). |
Collapse file Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_ticker.py +48-1Lines changed: 48 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 |
| 4 | 4 | from matplotlib.externals import six | 5 | 5 | import nose.tools | 6 | | -from nose.tools import assert_raises | | 6 | +from nose.tools import assert_equal, assert_raises | 7 | 7 | from numpy.testing import assert_almost_equal | 8 | 8 | import numpy as np | 9 | 9 | import matplotlib | @@ -159,6 +159,53 @@ def test_SymmetricalLogLocator_set_params(): | 159 | 159 | nose.tools.assert_equal(sym.numticks, 8) | 160 | 160 |
| 161 | 161 |
| | 162 | +@cleanup | | 163 | +def test_ScalarFormatter_offset_value(): | | 164 | + fig, ax = plt.subplots() | | 165 | + formatter = ax.get_xaxis().get_major_formatter() | | 166 | + | | 167 | + def check_offset_for(left, right, offset): | | 168 | + ax.set_xlim(left, right) | | 169 | + # Update ticks. | | 170 | + next(ax.get_xaxis().iter_ticks()) | | 171 | + assert_equal(formatter.offset, offset) | | 172 | + | | 173 | + test_data = [(123, 189, 0), | | 174 | + (-189, -123, 0), | | 175 | + (12341, 12349, 12340), | | 176 | + (-12349, -12341, -12340), | | 177 | + (99999.5, 100010.5, 100000), | | 178 | + (-100010.5, -99999.5, -100000), | | 179 | + (99990.5, 100000.5, 100000), | | 180 | + (-100000.5, -99990.5, -100000), | | 181 | + (1233999, 1234001, 1234000), | | 182 | + (-1234001, -1233999, -1234000), | | 183 | + (1, 1, 1), | | 184 | + (123, 123, 120), | | 185 | + # Test cases courtesy of @WeatherGod | | 186 | + (.4538, .4578, .45), | | 187 | + (3789.12, 3783.1, 3780), | | 188 | + (45124.3, 45831.75, 45000), | | 189 | + (0.000721, 0.0007243, 0.00072), | | 190 | + (12592.82, 12591.43, 12590), | | 191 | + (9., 12., 0), | | 192 | + (900., 1200., 0), | | 193 | + (1900., 1200., 0), | | 194 | + (0.99, 1.01, 1), | | 195 | + (9.99, 10.01, 10), | | 196 | + (99.99, 100.01, 100), | | 197 | + (5.99, 6.01, 6), | | 198 | + (15.99, 16.01, 16), | | 199 | + (-0.452, 0.492, 0), | | 200 | + (-0.492, 0.492, 0), | | 201 | + (12331.4, 12350.5, 12300), | | 202 | + (-12335.3, 12335.3, 0)] | | 203 | + | | 204 | + for left, right, offset in test_data: | | 205 | + yield check_offset_for, left, right, offset | | 206 | + yield check_offset_for, right, left, offset | | 207 | + | | 208 | + | 162 | 209 | def _logfe_helper(formatter, base, locs, i, expected_result): | 163 | 210 | vals = base**locs | 164 | 211 | labels = [formatter(x, pos) for (x, pos) in zip(vals, i)] | |
Collapse file Copy file name to clipboardExpand all lines: lib/matplotlib/ticker.py +36-18Lines changed: 36 additions & 18 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 159 | 159 | from matplotlib.externals import six | 160 | 160 |
| 161 | 161 | import decimal | | 162 | +import itertools | 162 | 163 | import locale | 163 | 164 | import math | 164 | 165 | import numpy as np | @@ -635,33 +636,50 @@ def set_locs(self, locs): | 635 | 636 | vmin, vmax = self.axis.get_view_interval() | 636 | 637 | d = abs(vmax - vmin) | 637 | 638 | if self._useOffset: | 638 | | - self._set_offset(d) | | 639 | + self._compute_offset() | 639 | 640 | self._set_orderOfMagnitude(d) | 640 | 641 | self._set_format(vmin, vmax) | 641 | 642 |
| 642 | | - def _set_offset(self, range): | 643 | | - # offset of 20,001 is 20,000, for example | | 643 | + def _compute_offset(self): | 644 | 644 | locs = self.locs | 645 | | - | 646 | | - if locs is None or not len(locs) or range == 0: | | 645 | + if locs is None or not len(locs): | 647 | 646 | self.offset = 0 | 648 | 647 | return | | 648 | + # Restrict to visible ticks. | 649 | 649 | vmin, vmax = sorted(self.axis.get_view_interval()) | 650 | 650 | locs = np.asarray(locs) | 651 | 651 | locs = locs[(vmin <= locs) & (locs <= vmax)] | 652 | | - ave_loc = np.mean(locs) | 653 | | - if len(locs) and ave_loc: # dont want to take log10(0) | 654 | | - ave_oom = math.floor(math.log10(np.mean(np.absolute(locs)))) | 655 | | - range_oom = math.floor(math.log10(range)) | 656 | | - | 657 | | - if np.absolute(ave_oom - range_oom) >= 3: # four sig-figs | 658 | | - p10 = 10 ** range_oom | 659 | | - if ave_loc < 0: | 660 | | - self.offset = (math.ceil(np.max(locs) / p10) * p10) | 661 | | - else: | 662 | | - self.offset = (math.floor(np.min(locs) / p10) * p10) | 663 | | - else: | 664 | | - self.offset = 0 | | 652 | + if not len(locs): | | 653 | + self.offset = 0 | | 654 | + return | | 655 | + lmin, lmax = locs.min(), locs.max() | | 656 | + # Only use offset if there are at least two ticks and every tick has | | 657 | + # the same sign. | | 658 | + if lmin == lmax or lmin <= 0 <= lmax: | | 659 | + self.offset = 0 | | 660 | + return | | 661 | + # min, max comparing absolute values (we want division to round towards | | 662 | + # zero so we work on absolute values). | | 663 | + abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))]) | | 664 | + sign = math.copysign(1, lmin) | | 665 | + # What is the smallest power of ten such that abs_min and abs_max are | | 666 | + # equal up to that precision? | | 667 | + # Note: Internally using oom instead of 10 ** oom avoids some numerical | | 668 | + # accuracy issues. | | 669 | + oom_max = math.ceil(math.log10(abs_max)) | | 670 | + oom = 1 + next(oom for oom in itertools.count(oom_max, -1) | | 671 | + if abs_min // 10 ** oom != abs_max // 10 ** oom) | | 672 | + if (abs_max - abs_min) / 10 ** oom <= 1e-2: | | 673 | + # Handle the case of straddling a multiple of a large power of ten | | 674 | + # (relative to the span). | | 675 | + # What is the smallest power of ten such that abs_min and abs_max | | 676 | + # are no more than 1 apart at that precision? | | 677 | + oom = 1 + next(oom for oom in itertools.count(oom_max, -1) | | 678 | + if abs_max // 10 ** oom - abs_min // 10 ** oom > 1) | | 679 | + # Only use offset if it saves at least two significant digits. | | 680 | + self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom | | 681 | + if abs_max // 10 ** oom >= 10 | | 682 | + else 0) | 665 | 683 |
| 666 | 684 | def _set_orderOfMagnitude(self, range): | 667 | 685 | # if scientific notation is to be used, find the appropriate exponent | |
|