| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,20 @@ | |||
| 1 | + `.matplotlib.Axes.get_tightbbox` now includes all artists | ||
| 2 | + --------------------------------------------------------- | ||
| 3 | + | ||
| 4 | + Layout tools like `.Figure.tight_layout`, ``constrained_layout``, | ||
| 5 | + and ``fig.savefig('fname.png', bbox_inches="tight")`` use | ||
| 6 | + `.matplotlib.Axes.get_tightbbox` to determine the bounds of each axes on | ||
| 7 | + a figure and adjust spacing between axes. | ||
| 8 | + | ||
| 9 | + In Matplotlib 2.2 ``get_tightbbox`` started to include legends made on the | ||
| 10 | + axes, but still excluded some other artists, like text that may overspill an | ||
| 11 | + axes. For Matplotlib 3.0, *all* artists are now included in the bounding box. | ||
| 12 | + | ||
| 13 | + This new default may be overridden in either of two ways: | ||
| 14 | + | ||
| 15 | + 1. Make the artist to be excluded a child of the figure, not the axes. E.g., | ||
| 16 | + call ``fig.legend()`` instead of ``ax.legend()`` (perhaps using | ||
| 17 | + `~.matplotlib.Axes.get_legend_handles_labels` to gather handles and labels | ||
| 18 | + from the parent axes). | ||
| 19 | + 2. If the artist is a child of the axes, set the artist property | ||
| 20 | + ``artist.set_in_layout(False)``. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -114,6 +114,7 @@ def __init__(self): | |||
| 114 | 114 | self._sketch = rcParams['path.sketch'] | |
| 115 | 115 | self._path_effects = rcParams['path.effects'] | |
| 116 | 116 | self._sticky_edges = _XYPair([], []) | |
| 117 | + self._in_layout = True | ||
| 117 | 118 | ||
| 118 | 119 | def __getstate__(self): | |
| 119 | 120 | d = self.__dict__.copy() | |
@@ -251,6 +252,33 @@ def get_window_extent(self, renderer): | |||
| 251 | 252 | """ | |
| 252 | 253 | return Bbox([[0, 0], [0, 0]]) | |
| 253 | 254 | ||
| 255 | + def get_tightbbox(self, renderer): | ||
| 256 | + """ | ||
| 257 | + Like `Artist.get_window_extent`, but includes any clipping. | ||
| 258 | + | ||
| 259 | + Parameters | ||
| 260 | + ---------- | ||
| 261 | + renderer : `.RendererBase` instance | ||
| 262 | + renderer that will be used to draw the figures (i.e. | ||
| 263 | + ``fig.canvas.get_renderer()``) | ||
| 264 | + | ||
| 265 | + Returns | ||
| 266 | + ------- | ||
| 267 | + bbox : `.BboxBase` | ||
| 268 | + containing the bounding box (in figure pixel co-ordinates). | ||
| 269 | + """ | ||
| 270 | + | ||
| 271 | + bbox = self.get_window_extent(renderer) | ||
| 272 | + if self.get_clip_on(): | ||
| 273 | + clip_box = self.get_clip_box() | ||
| 274 | + if clip_box is not None: | ||
| 275 | + bbox = Bbox.intersection(bbox, clip_box) | ||
| 276 | + clip_path = self.get_clip_path() | ||
| 277 | + if clip_path is not None and bbox is not None: | ||
| 278 | + clip_path = clip_path.get_fully_transformed_path() | ||
| 279 | + bbox = Bbox.intersection(bbox, clip_path.get_extents()) | ||
| 280 | + return bbox | ||
| 281 | + | ||
| 254 | 282 | def add_callback(self, func): | |
| 255 | 283 | """ | |
| 256 | 284 | Adds a callback function that will be called whenever one of | |
@@ -710,6 +738,17 @@ def get_animated(self): | |||
| 710 | 738 | "Return the artist's animated state" | |
| 711 | 739 | return self._animated | |
| 712 | 740 | ||
| 741 | + def get_in_layout(self): | ||
| 742 | + """ | ||
| 743 | + Return boolean flag, ``True`` if artist is included in layout | ||
| 744 | + calculations. | ||
| 745 | + | ||
| 746 | + E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, | ||
| 747 | + `.Figure.tight_layout()`, and | ||
| 748 | + ``fig.savefig(fname, bbox_inches='tight')``. | ||
| 749 | + """ | ||
| 750 | + return self._in_layout | ||
| 751 | + | ||
| 713 | 752 | def get_clip_on(self): | |
| 714 | 753 | 'Return whether artist uses clipping' | |
| 715 | 754 | return self._clipon | |
@@ -845,6 +884,19 @@ def set_animated(self, b): | |||
| 845 | 884 | self._animated = b | |
| 846 | 885 | self.pchanged() | |
| 847 | 886 | ||
| 887 | + def set_in_layout(self, in_layout): | ||
| 888 | + """ | ||
| 889 | + Set if artist is to be included in layout calculations, | ||
| 890 | + E.g. :doc:`/tutorials/intermediate/constrained_layout`, | ||
| 891 | + `.Figure.tight_layout()`, and | ||
| 892 | + ``fig.savefig(fname, bbox_inches='tight')``. | ||
| 893 | + | ||
| 894 | + Parameters | ||
| 895 | + ---------- | ||
| 896 | + in_layout : bool | ||
| 897 | + """ | ||
| 898 | + self._in_layout = in_layout | ||
| 899 | + | ||
| 848 | 900 | def update(self, props): | |
| 849 | 901 | """ | |
| 850 | 902 | Update this artist's properties from the dictionary *prop*. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4108,19 +4108,47 @@ def pick(self, *args): | |||
| 4108 | 4108 | martist.Artist.pick(self, args[0]) | |
| 4109 | 4109 | ||
| 4110 | 4110 | def get_default_bbox_extra_artists(self): | |
| 4111 | + """ | ||
| 4112 | + Return a default list of artists that are used for the bounding box | ||
| 4113 | + calculation. | ||
| 4114 | + | ||
| 4115 | + Artists are excluded either by not being visible or | ||
| 4116 | + ``artist.set_in_layout(False)``. | ||
| 4117 | + """ | ||
| 4111 | 4118 | return [artist for artist in self.get_children() | |
| 4112 | - if artist.get_visible()] | ||
| 4119 | + if (artist.get_visible() and artist.get_in_layout())] | ||
| 4113 | 4120 | ||
| 4114 | - def get_tightbbox(self, renderer, call_axes_locator=True): | ||
| 4121 | + def get_tightbbox(self, renderer, call_axes_locator=True, | ||
| 4122 | + bbox_extra_artists=None): | ||
| 4115 | 4123 | """ | |
| 4116 | - Return the tight bounding box of the axes. | ||
| 4117 | - The dimension of the Bbox in canvas coordinate. | ||
| 4124 | + Return the tight bounding box of the axes, including axis and their | ||
| 4125 | + decorators (xlabel, title, etc). | ||
| 4126 | + | ||
| 4127 | + Artists that have ``artist.set_in_layout(False)`` are not included | ||
| 4128 | + in the bbox. | ||
| 4129 | + | ||
| 4130 | + Parameters | ||
| 4131 | + ---------- | ||
| 4132 | + renderer : `.RendererBase` instance | ||
| 4133 | + renderer that will be used to draw the figures (i.e. | ||
| 4134 | + ``fig.canvas.get_renderer()``) | ||
| 4135 | + | ||
| 4136 | + bbox_extra_artists : list of `.Artist` or ``None`` | ||
| 4137 | + List of artists to include in the tight bounding box. If | ||
| 4138 | + ``None`` (default), then all artist children of the axes are | ||
| 4139 | + included in the tight bounding box. | ||
| 4140 | + | ||
| 4141 | + call_axes_locator : boolean (default ``True``) | ||
| 4142 | + If *call_axes_locator* is ``False``, it does not call the | ||
| 4143 | + ``_axes_locator`` attribute, which is necessary to get the correct | ||
| 4144 | + bounding box. ``call_axes_locator=False`` can be used if the | ||
| 4145 | + caller is only interested in the relative size of the tightbbox | ||
| 4146 | + compared to the axes bbox. | ||
| 4118 | 4147 | ||
| 4119 | - If *call_axes_locator* is *False*, it does not call the | ||
| 4120 | - _axes_locator attribute, which is necessary to get the correct | ||
| 4121 | - bounding box. ``call_axes_locator==False`` can be used if the | ||
| 4122 | - caller is only intereted in the relative size of the tightbbox | ||
| 4123 | - compared to the axes bbox. | ||
| 4148 | + Returns | ||
| 4149 | + ------- | ||
| 4150 | + bbox : `.BboxBase` | ||
| 4151 | + bounding box in figure pixel coordinates. | ||
| 4124 | 4152 | """ | |
| 4125 | 4153 | ||
| 4126 | 4154 | bb = [] | |
@@ -4153,11 +4181,14 @@ def get_tightbbox(self, renderer, call_axes_locator=True): | |||
| 4153 | 4181 | if bb_yaxis: | |
| 4154 | 4182 | bb.append(bb_yaxis) | |
| 4155 | 4183 | ||
| 4156 | - for child in self.get_children(): | ||
| 4157 | - if isinstance(child, OffsetBox) and child.get_visible(): | ||
| 4158 | - bb.append(child.get_window_extent(renderer)) | ||
| 4159 | - elif isinstance(child, Legend) and child.get_visible(): | ||
| 4160 | - bb.append(child._legend_box.get_window_extent(renderer)) | ||
| 4184 | + bbox_artists = bbox_extra_artists | ||
| 4185 | + if bbox_artists is None: | ||
| 4186 | + bbox_artists = self.get_default_bbox_extra_artists() | ||
| 4187 | + | ||
| 4188 | + for a in bbox_artists: | ||
| 4189 | + bbox = a.get_tightbbox(renderer) | ||
| 4190 | + if bbox is not None and (bbox.width != 0 or bbox.height != 0): | ||
| 4191 | + bb.append(bbox) | ||
| 4161 | 4192 | ||
| 4162 | 4193 | _bbox = mtransforms.Bbox.union( | |
| 4163 | 4194 | [b for b in bb if b.width != 0 or b.height != 0]) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2060,36 +2060,9 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None, | |||
| 2060 | 2060 | dryrun=True, | |
| 2061 | 2061 | **kwargs) | |
| 2062 | 2062 | renderer = self.figure._cachedRenderer | |
| 2063 | - bbox_inches = self.figure.get_tightbbox(renderer) | ||
| 2064 | - | ||
| 2065 | 2063 | bbox_artists = kwargs.pop("bbox_extra_artists", None) | |
| 2066 | - if bbox_artists is None: | ||
| 2067 | - bbox_artists = \ | ||
| 2068 | - self.figure.get_default_bbox_extra_artists() | ||
| 2069 | - | ||
| 2070 | - bbox_filtered = [] | ||
| 2071 | - for a in bbox_artists: | ||
| 2072 | - bbox = a.get_window_extent(renderer) | ||
| 2073 | - if a.get_clip_on(): | ||
| 2074 | - clip_box = a.get_clip_box() | ||
| 2075 | - if clip_box is not None: | ||
| 2076 | - bbox = Bbox.intersection(bbox, clip_box) | ||
| 2077 | - clip_path = a.get_clip_path() | ||
| 2078 | - if clip_path is not None and bbox is not None: | ||
| 2079 | - clip_path = \ | ||
| 2080 | - clip_path.get_fully_transformed_path() | ||
| 2081 | - bbox = Bbox.intersection( | ||
| 2082 | - bbox, clip_path.get_extents()) | ||
| 2083 | - if bbox is not None and ( | ||
| 2084 | - bbox.width != 0 or bbox.height != 0): | ||
| 2085 | - bbox_filtered.append(bbox) | ||
| 2086 | - | ||
| 2087 | - if bbox_filtered: | ||
| 2088 | - _bbox = Bbox.union(bbox_filtered) | ||
| 2089 | - trans = Affine2D().scale(1.0 / self.figure.dpi) | ||
| 2090 | - bbox_extra = TransformedBbox(_bbox, trans) | ||
| 2091 | - bbox_inches = Bbox.union([bbox_inches, bbox_extra]) | ||
| 2092 | - | ||
| 2064 | + bbox_inches = self.figure.get_tightbbox(renderer, | ||
| 2065 | + bbox_extra_artists=bbox_artists) | ||
| 2093 | 2066 | pad = kwargs.pop("pad_inches", None) | |
| 2094 | 2067 | if pad is None: | |
| 2095 | 2068 | pad = rcParams['savefig.pad_inches'] | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1464,10 +1464,7 @@ def draw(self, renderer): | |||
| 1464 | 1464 | try: | |
| 1465 | 1465 | renderer.open_group('figure') | |
| 1466 | 1466 | if self.get_constrained_layout() and self.axes: | |
| 1467 | - if True: | ||
| 1468 | - self.execute_constrained_layout(renderer) | ||
| 1469 | - else: | ||
| 1470 | - pass | ||
| 1467 | + self.execute_constrained_layout(renderer) | ||
| 1471 | 1468 | if self.get_tight_layout() and self.axes: | |
| 1472 | 1469 | try: | |
| 1473 | 1470 | self.tight_layout(renderer, | |
@@ -2008,26 +2005,52 @@ def waitforbuttonpress(self, timeout=-1): | |||
| 2008 | 2005 | ||
| 2009 | 2006 | def get_default_bbox_extra_artists(self): | |
| 2010 | 2007 | bbox_artists = [artist for artist in self.get_children() | |
| 2011 | - if artist.get_visible()] | ||
| 2008 | + if (artist.get_visible() and artist.get_in_layout())] | ||
| 2012 | 2009 | for ax in self.axes: | |
| 2013 | 2010 | if ax.get_visible(): | |
| 2014 | 2011 | bbox_artists.extend(ax.get_default_bbox_extra_artists()) | |
| 2015 | 2012 | # we don't want the figure's patch to influence the bbox calculation | |
| 2016 | 2013 | bbox_artists.remove(self.patch) | |
| 2017 | 2014 | return bbox_artists | |
| 2018 | 2015 | ||
| 2019 | - def get_tightbbox(self, renderer): | ||
| 2016 | + def get_tightbbox(self, renderer, bbox_extra_artists=None): | ||
| 2020 | 2017 | """ | |
| 2021 | 2018 | Return a (tight) bounding box of the figure in inches. | |
| 2022 | 2019 | ||
| 2023 | - Currently, this takes only axes title, axis labels, and axis | ||
| 2024 | - ticklabels into account. Needs improvement. | ||
| 2020 | + Artists that have ``artist.set_in_layout(False)`` are not included | ||
| 2021 | + in the bbox. | ||
| 2022 | + | ||
| 2023 | + Parameters | ||
| 2024 | + ---------- | ||
| 2025 | + renderer : `.RendererBase` instance | ||
| 2026 | + renderer that will be used to draw the figures (i.e. | ||
| 2027 | + ``fig.canvas.get_renderer()``) | ||
| 2028 | + | ||
| 2029 | + bbox_extra_artists : list of `.Artist` or ``None`` | ||
| 2030 | + List of artists to include in the tight bounding box. If | ||
| 2031 | + ``None`` (default), then all artist children of each axes are | ||
| 2032 | + included in the tight bounding box. | ||
| 2033 | + | ||
| 2034 | + Returns | ||
| 2035 | + ------- | ||
| 2036 | + bbox : `.BboxBase` | ||
| 2037 | + containing the bounding box (in figure inches). | ||
| 2025 | 2038 | """ | |
| 2026 | 2039 | ||
| 2027 | 2040 | bb = [] | |
| 2041 | + if bbox_extra_artists is None: | ||
| 2042 | + artists = self.get_default_bbox_extra_artists() | ||
| 2043 | + else: | ||
| 2044 | + artists = bbox_extra_artists | ||
| 2045 | + | ||
| 2046 | + for a in artists: | ||
| 2047 | + bbox = a.get_tightbbox(renderer) | ||
| 2048 | + if bbox is not None and (bbox.width != 0 or bbox.height != 0): | ||
| 2049 | + bb.append(bbox) | ||
| 2050 | + | ||
| 2028 | 2051 | for ax in self.axes: | |
| 2029 | 2052 | if ax.get_visible(): | |
| 2030 | - bb.append(ax.get_tightbbox(renderer)) | ||
| 2053 | + bb.append(ax.get_tightbbox(renderer, bbox_extra_artists)) | ||
| 2031 | 2054 | ||
| 2032 | 2055 | if len(bb) == 0: | |
| 2033 | 2056 | return self.bbox_inches | |
@@ -2079,6 +2102,10 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, | |||
| 2079 | 2102 | """ | |
| 2080 | 2103 | Automatically adjust subplot parameters to give specified padding. | |
| 2081 | 2104 | ||
| 2105 | + To exclude an artist on the axes from the bounding box calculation | ||
| 2106 | + that determines the subplot parameters (i.e. legend, or annotation), | ||
| 2107 | + then set `a.set_in_layout(False)` for that artist. | ||
| 2108 | + | ||
| 2082 | 2109 | Parameters | |
| 2083 | 2110 | ---------- | |
| 2084 | 2111 | pad : float | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -980,6 +980,22 @@ def get_window_extent(self, *args, **kwargs): | |||
| 980 | 980 | 'Return extent of the legend.' | |
| 981 | 981 | return self._legend_box.get_window_extent(*args, **kwargs) | |
| 982 | 982 | ||
| 983 | + def get_tightbbox(self, renderer): | ||
| 984 | + """ | ||
| 985 | + Like `.Legend.get_window_extent`, but uses the box for the legend. | ||
| 986 | + | ||
| 987 | + Parameters | ||
| 988 | + ---------- | ||
| 989 | + renderer : `.RendererBase` instance | ||
| 990 | + renderer that will be used to draw the figures (i.e. | ||
| 991 | + ``fig.canvas.get_renderer()``) | ||
| 992 | + | ||
| 993 | + Returns | ||
| 994 | + ------- | ||
| 995 | + `.BboxBase` : containing the bounding box in figure pixel co-ordinates. | ||
| 996 | + """ | ||
| 997 | + return self._legend_box.get_window_extent(renderer) | ||
| 998 | + | ||
| 983 | 999 | def get_frame_on(self): | |
| 984 | 1000 | """Get whether the legend box patch is drawn.""" | |
| 985 | 1001 | return self._drawFrame | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -383,3 +383,28 @@ def test_fspath(fmt, tmpdir): | |||
| 383 | 383 | # All the supported formats include the format name (case-insensitive) | |
| 384 | 384 | # in the first 100 bytes. | |
| 385 | 385 | assert fmt.encode("ascii") in file.read(100).lower() | |
| 386 | + | ||
| 387 | + | ||
| 388 | + def test_tightbbox(): | ||
| 389 | + fig, ax = plt.subplots() | ||
| 390 | + ax.set_xlim(0, 1) | ||
| 391 | + t = ax.text(1., 0.5, 'This dangles over end') | ||
| 392 | + renderer = fig.canvas.get_renderer() | ||
| 393 | + x1Nom0 = 9.035 # inches | ||
| 394 | + assert np.abs(t.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2 | ||
| 395 | + assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2 | ||
| 396 | + assert np.abs(fig.get_tightbbox(renderer).x1 - x1Nom0) < 0.05 | ||
| 397 | + assert np.abs(fig.get_tightbbox(renderer).x0 - 0.679) < 0.05 | ||
| 398 | + # now exclude t from the tight bbox so now the bbox is quite a bit | ||
| 399 | + # smaller | ||
| 400 | + t.set_in_layout(False) | ||
| 401 | + x1Nom = 7.333 | ||
| 402 | + assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom * fig.dpi) < 2 | ||
| 403 | + assert np.abs(fig.get_tightbbox(renderer).x1 - x1Nom) < 0.05 | ||
| 404 | + | ||
| 405 | + t.set_in_layout(True) | ||
| 406 | + x1Nom = 7.333 | ||
| 407 | + assert np.abs(ax.get_tightbbox(renderer).x1 - x1Nom0 * fig.dpi) < 2 | ||
| 408 | + # test bbox_extra_artists method... | ||
| 409 | + assert np.abs(ax.get_tightbbox(renderer, | ||
| 410 | + bbox_extra_artists=[]).x1 - x1Nom * fig.dpi) < 2 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -189,7 +189,7 @@ def example_plot(ax, fontsize=12, nodec=False): | |||
| 189 | 189 | ||
| 190 | 190 | fig, ax = plt.subplots(constrained_layout=True) | |
| 191 | 191 | ax.plot(np.arange(10), label='This is a plot') | |
| 192 | - ax.legend(loc='center left', bbox_to_anchor=(0.9, 0.5)) | ||
| 192 | + ax.legend(loc='center left', bbox_to_anchor=(0.8, 0.5)) | ||
| 193 | 193 | ||
| 194 | 194 | ############################################# | |
| 195 | 195 | # However, this will steal space from a subplot layout: | |
@@ -198,7 +198,39 @@ def example_plot(ax, fontsize=12, nodec=False): | |||
| 198 | 198 | for ax in axs.flatten()[:-1]: | |
| 199 | 199 | ax.plot(np.arange(10)) | |
| 200 | 200 | axs[1, 1].plot(np.arange(10), label='This is a plot') | |
| 201 | - axs[1, 1].legend(loc='center left', bbox_to_anchor=(0.9, 0.5)) | ||
| 201 | + axs[1, 1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5)) | ||
| 202 | + | ||
| 203 | + ############################################# | ||
| 204 | + # In order for a legend or other artist to *not* steal space | ||
| 205 | + # from the subplot layout, we can ``leg.set_in_layout(False)``. | ||
| 206 | + # Of course this can mean the legend ends up | ||
| 207 | + # cropped, but can be useful if the plot is subsequently called | ||
| 208 | + # with ``fig.savefig('outname.png', bbox_inches='tight')``. Note, | ||
| 209 | + # however, that the legend's ``get_in_layout`` status will have to be | ||
| 210 | + # toggled again to make the saved file work: | ||
| 211 | + | ||
| 212 | + fig, axs = plt.subplots(2, 2, constrained_layout=True) | ||
| 213 | + for ax in axs.flatten()[:-1]: | ||
| 214 | + ax.plot(np.arange(10)) | ||
| 215 | + axs[1, 1].plot(np.arange(10), label='This is a plot') | ||
| 216 | + leg = axs[1, 1].legend(loc='center left', bbox_to_anchor=(0.8, 0.5)) | ||
| 217 | + leg.set_in_layout(False) | ||
| 218 | + wanttoprint = False | ||
| 219 | + if wanttoprint: | ||
| 220 | + leg.set_in_layout(True) | ||
| 221 | + fig.do_constrained_layout(False) | ||
| 222 | + fig.savefig('outname.png', bbox_inches='tight') | ||
| 223 | + | ||
| 224 | + ############################################# | ||
| 225 | + # A better way to get around this awkwardness is to simply | ||
| 226 | + # use a legend for the figure: | ||
| 227 | + fig, axs = plt.subplots(2, 2, constrained_layout=True) | ||
| 228 | + for ax in axs.flatten()[:-1]: | ||
| 229 | + ax.plot(np.arange(10)) | ||
| 230 | + lines = axs[1, 1].plot(np.arange(10), label='This is a plot') | ||
| 231 | + labels = [l.get_label() for l in lines] | ||
| 232 | + leg = fig.legend(lines, labels, loc='center left', | ||
| 233 | + bbox_to_anchor=(0.8, 0.5), bbox_transform=axs[1, 1].transAxes) | ||
| 202 | 234 | ||
| 203 | 235 | ############################################################################### | |
| 204 | 236 | # Padding and Spacing | |
| Back | FazBrowse Home | New Git URL |
0 commit comments