diff options
author | Matthew Sotoudeh <matthewsot@outlook.com> | 2017-07-27 09:07:48 -0700 |
---|---|---|
committer | Matthew Sotoudeh <matthewsot@outlook.com> | 2017-07-27 09:07:48 -0700 |
commit | 498aa8d2ca7be1880e5df0c4e3e15ea9bdf60919 (patch) | |
tree | 0dca4e3a4f96a11dd624db6bc059126548da06d3 | |
parent | 63744d59c4bf999a0ba7bd5b7f7080f830aedeb7 (diff) |
Refactored the legend placement code into cbookauto-placement
-rw-r--r-- | lib/matplotlib/cbook/__init__.py | 93 | ||||
-rw-r--r-- | lib/matplotlib/legend.py | 95 |
2 files changed, 95 insertions, 93 deletions
diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 9a143cda4..6caa6ca48 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -2707,3 +2707,96 @@ class _StringFuncParser(object): (params, str_func)) return str_func, params + +def _auto_legend_data(ax): + """ + Returns list of vertices and extents covered by the plot. + + Returns a two long list. + + First element is a list of (x, y) vertices (in + display-coordinates) covered by all the lines and line + collections, in the legend's handles. + + Second element is a list of bounding boxes for all the patches in + the legend's handles. + """ + + bboxes = [] + lines = [] + offsets = [] + + from matplotlib.lines import Line2D + from matplotlib.patches import Patch, Rectangle + + for handle in ax.lines: + assert isinstance(handle, Line2D) + path = handle.get_path() + trans = handle.get_transform() + tpath = trans.transform_path(path) + lines.append(tpath) + + for handle in ax.patches: + assert isinstance(handle, Patch) + + if isinstance(handle, Rectangle): + transform = handle.get_data_transform() + bboxes.append(handle.get_bbox().transformed(transform)) + else: + transform = handle.get_transform() + bboxes.append(handle.get_path().get_extents(transform)) + + for handle in ax.collections: + transform, transOffset, hoffsets, paths = handle._prepare_points() + + if len(hoffsets): + for offset in transOffset.transform(hoffsets): + offsets.append(offset) + + try: + vertices = np.concatenate([l.vertices for l in lines]) + except ValueError: + vertices = np.array([]) + + return [vertices, bboxes, lines, offsets] + +def find_best_position(artist, width, height, renderer, consider=None): + """ + Determine the best location to place the legend. + + `consider` is a list of (x, y) pairs to consider as a potential + lower-left corner of the legend. All are display coords. + """ + from matplotlib.transforms import Bbox + + # should always hold because function is only called internally + assert artist.isaxes + + verts, bboxes, lines, offsets = _auto_legend_data(artist.parent) + + bbox = Bbox.from_bounds(0, 0, width, height) + if consider is None: + consider = [artist._get_anchored_bbox(x, bbox, + artist.get_bbox_to_anchor(), + renderer) + for x in range(1, len(artist.codes))] + + candidates = [] + for idx, (l, b) in enumerate(consider): + legendBox = Bbox.from_bounds(l, b, width, height) + badness = 0 + # XXX TODO: If markers are present, it would be good to + # take them into account when checking vertex overlaps in + # the next line. + badness = (legendBox.count_contains(verts) + + legendBox.count_contains(offsets) + + legendBox.count_overlaps(bboxes) + + sum(line.intersects_bbox(legendBox, filled=False) + for line in lines)) + if badness == 0: + return l, b + # Include the index to favor lower codes in case of a tie. + candidates.append((badness, idx, (l, b))) + + _, _, (l, b) = min(candidates) + return l, b diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 2b99587e8..2ec62ae8f 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -34,7 +34,7 @@ import numpy as np from matplotlib import rcParams from matplotlib.artist import Artist, allow_rasterization -from matplotlib.cbook import silent_list, is_hashable +from matplotlib.cbook import silent_list, is_hashable, find_best_position from matplotlib.font_manager import FontProperties from matplotlib.lines import Line2D from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch @@ -421,7 +421,7 @@ class Legend(Artist): "Helper function to locate the legend" if self._loc == 0: # "best". - x, y = self._find_best_position(width, height, renderer) + x, y = find_best_position(self, width, height, renderer) elif self._loc in Legend.codes.values(): # Fixed location. bbox = Bbox.from_bounds(0, 0, width, height) x, y = self._get_anchored_bbox(self._loc, bbox, @@ -691,58 +691,6 @@ class Legend(Artist): self.texts = text_list self.legendHandles = handle_list - def _auto_legend_data(self): - """ - Returns list of vertices and extents covered by the plot. - - Returns a two long list. - - First element is a list of (x, y) vertices (in - display-coordinates) covered by all the lines and line - collections, in the legend's handles. - - Second element is a list of bounding boxes for all the patches in - the legend's handles. - """ - # should always hold because function is only called internally - assert self.isaxes - - ax = self.parent - bboxes = [] - lines = [] - offsets = [] - - for handle in ax.lines: - assert isinstance(handle, Line2D) - path = handle.get_path() - trans = handle.get_transform() - tpath = trans.transform_path(path) - lines.append(tpath) - - for handle in ax.patches: - assert isinstance(handle, Patch) - - if isinstance(handle, Rectangle): - transform = handle.get_data_transform() - bboxes.append(handle.get_bbox().transformed(transform)) - else: - transform = handle.get_transform() - bboxes.append(handle.get_path().get_extents(transform)) - - for handle in ax.collections: - transform, transOffset, hoffsets, paths = handle._prepare_points() - - if len(hoffsets): - for offset in transOffset.transform(hoffsets): - offsets.append(offset) - - try: - vertices = np.concatenate([l.vertices for l in lines]) - except ValueError: - vertices = np.array([]) - - return [vertices, bboxes, lines, offsets] - def draw_frame(self, b): 'b is a boolean. Set draw frame to b' self.set_frame_on(b) @@ -891,45 +839,6 @@ class Legend(Artist): anchored_box = bbox.anchored(c, container=container) return anchored_box.x0, anchored_box.y0 - def _find_best_position(self, width, height, renderer, consider=None): - """ - Determine the best location to place the legend. - - `consider` is a list of (x, y) pairs to consider as a potential - lower-left corner of the legend. All are display coords. - """ - # should always hold because function is only called internally - assert self.isaxes - - verts, bboxes, lines, offsets = self._auto_legend_data() - - bbox = Bbox.from_bounds(0, 0, width, height) - if consider is None: - consider = [self._get_anchored_bbox(x, bbox, - self.get_bbox_to_anchor(), - renderer) - for x in range(1, len(self.codes))] - - candidates = [] - for idx, (l, b) in enumerate(consider): - legendBox = Bbox.from_bounds(l, b, width, height) - badness = 0 - # XXX TODO: If markers are present, it would be good to - # take them into account when checking vertex overlaps in - # the next line. - badness = (legendBox.count_contains(verts) - + legendBox.count_contains(offsets) - + legendBox.count_overlaps(bboxes) - + sum(line.intersects_bbox(legendBox, filled=False) - for line in lines)) - if badness == 0: - return l, b - # Include the index to favor lower codes in case of a tie. - candidates.append((badness, idx, (l, b))) - - _, _, (l, b) = min(candidates) - return l, b - def contains(self, event): return self.legendPatch.contains(event) |