From 46fea7f08a325d00fe79050a084a16402c3670f6 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Tue, 5 Feb 2019 14:43:43 +0100 Subject: [PATCH 1/2] Inline iter_ticks into _update_ticks, and use that in mplot3d. iter_ticks is only called in one place and the iterator is immediately converted into a list, so we may as well inline it and create the list immediately. Also, we can immediately assign the tick locations and labels, instead of having to carry around the ticks, the locations and the labels as a tuple. Reuse that implementation in axis3d (... which also gains support for minor ticks for free). --- doc/api/axis_api.rst | 3 - doc/api/next_api_changes/2018-02-05-AL.rst | 8 +++ lib/matplotlib/axis.py | 75 ++++++++++++---------- lib/matplotlib/tests/test_ticker.py | 6 +- lib/mpl_toolkits/mplot3d/axis3d.py | 29 ++------- 5 files changed, 58 insertions(+), 63 deletions(-) create mode 100644 doc/api/next_api_changes/2018-02-05-AL.rst diff --git a/doc/api/axis_api.rst b/doc/api/axis_api.rst index ad5f75d57c82..3889b86498cb 100644 --- a/doc/api/axis_api.rst +++ b/doc/api/axis_api.rst @@ -103,7 +103,6 @@ Ticks, tick labels and Offset text Axis.get_gridlines Axis.grid - Axis.iter_ticks Axis.set_tick_params Axis.axis_date @@ -365,7 +364,6 @@ YAxis YAxis.get_units YAxis.get_view_interval YAxis.grid - YAxis.iter_ticks YAxis.limit_range_for_scale YAxis.pan YAxis.reset_ticks @@ -432,7 +430,6 @@ XAxis XAxis.get_units XAxis.get_view_interval XAxis.grid - XAxis.iter_ticks XAxis.limit_range_for_scale XAxis.pan XAxis.reset_ticks diff --git a/doc/api/next_api_changes/2018-02-05-AL.rst b/doc/api/next_api_changes/2018-02-05-AL.rst new file mode 100644 index 000000000000..48c7ced597ff --- /dev/null +++ b/doc/api/next_api_changes/2018-02-05-AL.rst @@ -0,0 +1,8 @@ +Changes to the internal tick handling API +````````````````````````````````````````` + +``Axis.iter_ticks`` (which only served as a helper to the private +``Axis._update_ticks``) is deprecated. + +The signature of the (private) ``Axis._update_ticks`` has been changed to not +take the renderer as argument anymore (that argument is unused). diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index da85450ba86e..1a903733de86 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1016,6 +1016,7 @@ def _set_artist_props(self, a): return a.set_figure(self.figure) + @cbook.deprecated("3.1") def iter_ticks(self): """ Yield ``(Tick, location, label)`` tuples for major and minor ticks. @@ -1035,7 +1036,7 @@ def get_ticklabel_extents(self, renderer): of the axes. """ - ticks_to_draw = self._update_ticks(renderer) + ticks_to_draw = self._update_ticks() ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw, renderer) @@ -1058,20 +1059,38 @@ def get_smart_bounds(self): """get whether the axis has smart bounds""" return self._smart_bounds - def _update_ticks(self, renderer): + def _update_ticks(self): """ - Update ticks (position and labels) using the current data - interval of the axes. Returns a list of ticks that will be - drawn. + Update ticks (position and labels) using the current data interval of + the axes. Return the list of ticks that will be drawn. """ - interval = self.get_view_interval() - tick_tups = list(self.iter_ticks()) # iter_ticks calls the locator - if self._smart_bounds and tick_tups: + major_locs = self.major.locator() + major_ticks = self.get_major_ticks(len(major_locs)) + self.major.formatter.set_locs(major_locs) + major_labels = self.major.formatter.format_ticks(major_locs) + for tick, loc, label in zip(major_ticks, major_locs, major_labels): + tick.update_position(loc) + tick.set_label1(label) + tick.set_label2(label) + minor_locs = self.minor.locator() + minor_ticks = self.get_minor_ticks(len(minor_locs)) + self.minor.formatter.set_locs(minor_locs) + minor_labels = self.minor.formatter.format_ticks(minor_locs) + for tick, loc, label in zip(minor_ticks, minor_locs, minor_labels): + tick.update_position(loc) + tick.set_label1(label) + tick.set_label2(label) + ticks = [*major_ticks, *minor_ticks] + + view_low, view_high = self.get_view_interval() + if view_low > view_high: + view_low, view_high = view_high, view_low + + if self._smart_bounds and ticks: # handle inverted limits - view_low, view_high = sorted(interval) data_low, data_high = sorted(self.get_data_interval()) - locs = np.sort([ti[1] for ti in tick_tups]) + locs = np.sort([tick.get_loc() for tick in ticks]) if data_low <= view_low: # data extends beyond view, take view as limit ilow = view_low @@ -1096,33 +1115,21 @@ def _update_ticks(self, renderer): else: # No ticks (why not?), take last tick ihigh = locs[-1] - tick_tups = [ti for ti in tick_tups if ilow <= ti[1] <= ihigh] + ticks = [tick for tick in ticks if ilow <= tick.get_loc() <= ihigh] - if interval[1] <= interval[0]: - interval = interval[1], interval[0] - inter = self.get_transform().transform(interval) + interval_t = self.get_transform().transform([view_low, view_high]) ticks_to_draw = [] - for tick, loc, label in tick_tups: - # draw each tick if it is in interval. Note the transform - # to pixel space to take care of log transforms etc. - # interval_contains has a floating point tolerance. - if tick is None: - continue - # NB: always update labels and position to avoid issues like #9397 - tick.update_position(loc) - tick.set_label1(label) - tick.set_label2(label) + for tick in ticks: try: - loct = self.get_transform().transform(loc) + loc_t = self.get_transform().transform(tick.get_loc()) except AssertionError: # transforms.transform doesn't allow masked values but # some scales might make them, so we need this try/except. - loct = None - continue - if not mtransforms._interval_contains_close(inter, loct): - continue - ticks_to_draw.append(tick) + pass + else: + if mtransforms._interval_contains_close(interval_t, loc_t): + ticks_to_draw.append(tick) return ticks_to_draw @@ -1141,7 +1148,7 @@ def get_tightbbox(self, renderer): if not self.get_visible(): return - ticks_to_draw = self._update_ticks(renderer) + ticks_to_draw = self._update_ticks() self._update_label_position(renderer) @@ -1182,7 +1189,7 @@ def draw(self, renderer, *args, **kwargs): return renderer.open_group(__name__) - ticks_to_draw = self._update_ticks(renderer) + ticks_to_draw = self._update_ticks() ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw, renderer) @@ -1948,7 +1955,7 @@ def _get_tick_boxes_siblings(self, renderer): grp = self.figure._align_xlabel_grp # if we want to align labels from other axes: for nn, axx in enumerate(grp.get_siblings(self.axes)): - ticks_to_draw = axx.xaxis._update_ticks(renderer) + ticks_to_draw = axx.xaxis._update_ticks() tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer) bboxes.extend(tlb) bboxes2.extend(tlb2) @@ -2262,7 +2269,7 @@ def _get_tick_boxes_siblings(self, renderer): grp = self.figure._align_ylabel_grp # if we want to align labels from other axes: for axx in grp.get_siblings(self.axes): - ticks_to_draw = axx.yaxis._update_ticks(renderer) + ticks_to_draw = axx.yaxis._update_ticks() tlb, tlb2 = axx.yaxis._get_tick_bboxes(ticks_to_draw, renderer) bboxes.extend(tlb) bboxes2.extend(tlb2) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 7d4ed3b1f616..b35a7fd45df8 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -346,8 +346,7 @@ def test_offset_value(self, left, right, offset): UserWarning) ax.set_xlim(left, right) assert len(w) == (1 if left == right else 0) - # Update ticks. - next(ax.get_xaxis().iter_ticks()) + ax.get_xaxis()._update_ticks() assert formatter.offset == offset with warnings.catch_warnings(record=True) as w: @@ -355,8 +354,7 @@ def test_offset_value(self, left, right, offset): UserWarning) ax.set_xlim(right, left) assert len(w) == (1 if left == right else 0) - # Update ticks. - next(ax.get_xaxis().iter_ticks()) + ax.get_xaxis()._update_ticks() assert formatter.offset == offset @pytest.mark.parametrize('use_offset', use_offset_data) diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index 8550a4407062..6f33d83c1849 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -223,22 +223,11 @@ def draw(self, renderer): self.label._transform = self.axes.transData renderer.open_group('axis3d') - # code from XAxis - majorTicks = self.get_major_ticks() - majorLocs = self.major.locator() + ticks = self._update_ticks() info = self._axinfo index = info['i'] - # filter locations here so that no extra grid lines are drawn - locmin, locmax = self.get_view_interval() - if locmin > locmax: - locmin, locmax = locmax, locmin - - # Rudimentary clipping - majorLocs = [loc for loc in majorLocs if locmin <= loc <= locmax] - majorLabels = self.major.formatter.format_ticks(majorLocs) - mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer) # Determine grid lines @@ -259,9 +248,9 @@ def draw(self, renderer): # Grid points where the planes meet xyz0 = [] - for val in majorLocs: + for tick in ticks: coord = minmax.copy() - coord[index] = val + coord[index] = tick.get_loc() xyz0.append(coord) # Draw labels @@ -373,14 +362,14 @@ def draw(self, renderer): xyz1 = copy.deepcopy(xyz0) newindex = (index + 1) % 3 newval = get_flip_min_max(xyz1[0], newindex, mins, maxs) - for i in range(len(majorLocs)): + for i in range(len(ticks)): xyz1[i][newindex] = newval # Grid points at end of the other plane xyz2 = copy.deepcopy(xyz0) newindex = (index + 2) % 3 newval = get_flip_min_max(xyz2[0], newindex, mins, maxs) - for i in range(len(majorLocs)): + for i in range(len(ticks)): xyz2[i][newindex] = newval lines = list(zip(xyz1, xyz0, xyz2)) @@ -401,13 +390,11 @@ def draw(self, renderer): else: ticksign = -1 - for tick, loc, label in zip(majorTicks, majorLocs, majorLabels): - if tick is None: - continue + for tick in ticks: # Get tick line positions pos = copy.copy(edgep1) - pos[index] = loc + pos[index] = tick.get_loc() pos[tickdir] = ( edgep1[tickdir] + info['tick']['outward_factor'] * ticksign * tickdelta) @@ -434,8 +421,6 @@ def draw(self, renderer): tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly)) tick.tick1line.set_linewidth(info['tick']['linewidth']) tick.tick1line.set_color(info['tick']['color']) - tick.set_label1(label) - tick.set_label2(label) tick.draw(renderer) renderer.close_group('axis3d') From 36dd9cd0cd597f923b8989661a37d8dce40db3ea Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 13 Mar 2019 11:43:39 +0100 Subject: [PATCH 2/2] Update test that changed due to minor tick overstrike logic. --- .../test_axes/minorticks_on_rcParams_both.png | Bin 20686 -> 0 bytes lib/matplotlib/tests/test_axes.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 lib/matplotlib/tests/baseline_images/test_axes/minorticks_on_rcParams_both.png diff --git a/lib/matplotlib/tests/baseline_images/test_axes/minorticks_on_rcParams_both.png b/lib/matplotlib/tests/baseline_images/test_axes/minorticks_on_rcParams_both.png deleted file mode 100644 index f8b929e34907d9f9c3454e2ee83effc3f6c3824e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20686 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