_tight_bbox.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """
  2. Helper module for the *bbox_inches* parameter in `.Figure.savefig`.
  3. """
  4. from matplotlib.transforms import Bbox, TransformedBbox, Affine2D
  5. def adjust_bbox(fig, bbox_inches, fixed_dpi=None):
  6. """
  7. Temporarily adjust the figure so that only the specified area
  8. (bbox_inches) is saved.
  9. It modifies fig.bbox, fig.bbox_inches,
  10. fig.transFigure._boxout, and fig.patch. While the figure size
  11. changes, the scale of the original figure is conserved. A
  12. function which restores the original values are returned.
  13. """
  14. origBbox = fig.bbox
  15. origBboxInches = fig.bbox_inches
  16. _boxout = fig.transFigure._boxout
  17. old_aspect = []
  18. locator_list = []
  19. sentinel = object()
  20. for ax in fig.axes:
  21. locator = ax.get_axes_locator()
  22. if locator is not None:
  23. ax.apply_aspect(locator(ax, None))
  24. locator_list.append(locator)
  25. current_pos = ax.get_position(original=False).frozen()
  26. ax.set_axes_locator(lambda a, r, _pos=current_pos: _pos)
  27. # override the method that enforces the aspect ratio on the Axes
  28. if 'apply_aspect' in ax.__dict__:
  29. old_aspect.append(ax.apply_aspect)
  30. else:
  31. old_aspect.append(sentinel)
  32. ax.apply_aspect = lambda pos=None: None
  33. def restore_bbox():
  34. for ax, loc, aspect in zip(fig.axes, locator_list, old_aspect):
  35. ax.set_axes_locator(loc)
  36. if aspect is sentinel:
  37. # delete our no-op function which un-hides the original method
  38. del ax.apply_aspect
  39. else:
  40. ax.apply_aspect = aspect
  41. fig.bbox = origBbox
  42. fig.bbox_inches = origBboxInches
  43. fig.transFigure._boxout = _boxout
  44. fig.transFigure.invalidate()
  45. fig.patch.set_bounds(0, 0, 1, 1)
  46. if fixed_dpi is None:
  47. fixed_dpi = fig.dpi
  48. tr = Affine2D().scale(fixed_dpi)
  49. dpi_scale = fixed_dpi / fig.dpi
  50. fig.bbox_inches = Bbox.from_bounds(0, 0, *bbox_inches.size)
  51. x0, y0 = tr.transform(bbox_inches.p0)
  52. w1, h1 = fig.bbox.size * dpi_scale
  53. fig.transFigure._boxout = Bbox.from_bounds(-x0, -y0, w1, h1)
  54. fig.transFigure.invalidate()
  55. fig.bbox = TransformedBbox(fig.bbox_inches, tr)
  56. fig.patch.set_bounds(x0 / w1, y0 / h1,
  57. fig.bbox.width / w1, fig.bbox.height / h1)
  58. return restore_bbox
  59. def process_figure_for_rasterizing(fig, bbox_inches_restore, fixed_dpi=None):
  60. """
  61. A function that needs to be called when figure dpi changes during the
  62. drawing (e.g., rasterizing). It recovers the bbox and re-adjust it with
  63. the new dpi.
  64. """
  65. bbox_inches, restore_bbox = bbox_inches_restore
  66. restore_bbox()
  67. r = adjust_bbox(fig, bbox_inches, fixed_dpi)
  68. return bbox_inches, r