|
| 1 | +# MGDTODO: Just included verbatim for now |
| 2 | + |
| 3 | +class PBox(list): |
| 4 | + ''' |
| 5 | + A left-bottom-width-height (lbwh) specification of a bounding box, |
| 6 | + such as is used to specify the position of an Axes object within |
| 7 | + a Figure. |
| 8 | + It is a 4-element list with methods for changing the size, shape, |
| 9 | + and position relative to its container. |
| 10 | + ''' |
| 11 | + coefs = {'C': (0.5, 0.5), |
| 12 | + 'SW': (0,0), |
| 13 | + 'S': (0.5, 0), |
| 14 | + 'SE': (1.0, 0), |
| 15 | + 'E': (1.0, 0.5), |
| 16 | + 'NE': (1.0, 1.0), |
| 17 | + 'N': (0.5, 1.0), |
| 18 | + 'NW': (0, 1.0), |
| 19 | + 'W': (0, 0.5)} |
| 20 | + def __init__(self, box, container=None, llur=False): |
| 21 | + if len(box) != 4: |
| 22 | + raise ValueError("Argument must be iterable of length 4") |
| 23 | + if llur: |
| 24 | + box = [box[0], box[1], box[2]-box[0], box[3]-box[1]] |
| 25 | + list.__init__(self, box) |
| 26 | + self.set_container(container) |
| 27 | + |
| 28 | + def as_llur(self): |
| 29 | + return [self[0], self[1], self[0]+self[2], self[1]+self[3]] |
| 30 | + |
| 31 | + def set_container(self, box=None): |
| 32 | + if box is None: |
| 33 | + box = self |
| 34 | + if len(box) != 4: |
| 35 | + raise ValueError("Argument must be iterable of length 4") |
| 36 | + self._container = list(box) |
| 37 | + |
| 38 | + def get_container(self, box): |
| 39 | + return self._container |
| 40 | + |
| 41 | + def anchor(self, c, container=None): |
| 42 | + ''' |
| 43 | + Shift to position c within its container. |
| 44 | +
|
| 45 | + c can be a sequence (cx, cy) where cx, cy range from 0 to 1, |
| 46 | + where 0 is left or bottom and 1 is right or top. |
| 47 | +
|
| 48 | + Alternatively, c can be a string: C for centered, |
| 49 | + S for bottom-center, SE for bottom-left, E for left, etc. |
| 50 | +
|
| 51 | + Optional arg container is the lbwh box within which the |
| 52 | + PBox is positioned; it defaults to the initial |
| 53 | + PBox. |
| 54 | + ''' |
| 55 | + if container is None: |
| 56 | + container = self._container |
| 57 | + l,b,w,h = container |
| 58 | + if isinstance(c, str): |
| 59 | + cx, cy = self.coefs[c] |
| 60 | + else: |
| 61 | + cx, cy = c |
| 62 | + W,H = self[2:] |
| 63 | + self[:2] = l + cx * (w-W), b + cy * (h-H) |
| 64 | + return self |
| 65 | + |
| 66 | + def shrink(self, mx, my): |
| 67 | + ''' |
| 68 | + Shrink the box by mx in the x direction and my in the y direction. |
| 69 | + The lower left corner of the box remains unchanged. |
| 70 | + Normally mx and my will be <= 1, but this is not enforced. |
| 71 | + ''' |
| 72 | + assert mx >= 0 and my >= 0 |
| 73 | + self[2:] = mx * self[2], my * self[3] |
| 74 | + return self |
| 75 | + |
| 76 | + def shrink_to_aspect(self, box_aspect, fig_aspect = 1): |
| 77 | + ''' |
| 78 | + Shrink the box so that it is as large as it can be while |
| 79 | + having the desired aspect ratio, box_aspect. |
| 80 | + If the box coordinates are relative--that is, fractions of |
| 81 | + a larger box such as a figure--then the physical aspect |
| 82 | + ratio of that figure is specified with fig_aspect, so |
| 83 | + that box_aspect can also be given as a ratio of the |
| 84 | + absolute dimensions, not the relative dimensions. |
| 85 | + ''' |
| 86 | + assert box_aspect > 0 and fig_aspect > 0 |
| 87 | + l,b,w,h = self._container |
| 88 | + H = w * box_aspect/fig_aspect |
| 89 | + if H <= h: |
| 90 | + W = w |
| 91 | + else: |
| 92 | + W = h * fig_aspect/box_aspect |
| 93 | + H = h |
| 94 | + self[2:] = W,H |
| 95 | + return self |
| 96 | + |
| 97 | + def splitx(self, *args): |
| 98 | + ''' |
| 99 | + e.g., PB.splitx(f1, f2, ...) |
| 100 | +
|
| 101 | + Returns a list of new PBoxes formed by |
| 102 | + splitting the original one (PB) with vertical lines |
| 103 | + at fractional positions f1, f2, ... |
| 104 | + ''' |
| 105 | + boxes = [] |
| 106 | + xf = [0] + list(args) + [1] |
| 107 | + l,b,w,h = self[:] |
| 108 | + for xf0, xf1 in zip(xf[:-1], xf[1:]): |
| 109 | + boxes.append(PBox([l+xf0*w, b, (xf1-xf0)*w, h])) |
| 110 | + return boxes |
| 111 | + |
| 112 | + def splity(self, *args): |
| 113 | + ''' |
| 114 | + e.g., PB.splity(f1, f2, ...) |
| 115 | +
|
| 116 | + Returns a list of new PBoxes formed by |
| 117 | + splitting the original one (PB) with horizontal lines |
| 118 | + at fractional positions f1, f2, ..., with y measured |
| 119 | + positive up. |
| 120 | + ''' |
| 121 | + boxes = [] |
| 122 | + yf = [0] + list(args) + [1] |
| 123 | + l,b,w,h = self[:] |
| 124 | + for yf0, yf1 in zip(yf[:-1], yf[1:]): |
| 125 | + boxes.append(PBox([l, b+yf0*h, w, (yf1-yf0)*h])) |
| 126 | + return boxes |
0 commit comments