faceted 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- faceted/__init__.py +1 -0
- faceted/examples/basic-grid-example.png +0 -0
- faceted/examples/cartopy-example-sharex-row.png +0 -0
- faceted/examples/cartopy-example.png +0 -0
- faceted/examples/colorbar-grid-example.png +0 -0
- faceted/examples/multi-colorbar-grid-example.png +0 -0
- faceted/faceted.py +639 -0
- faceted/tests/__init__.py +0 -0
- faceted/tests/test_faceted.py +696 -0
- faceted-0.2.2.dist-info/METADATA +122 -0
- faceted-0.2.2.dist-info/RECORD +14 -0
- faceted-0.2.2.dist-info/WHEEL +5 -0
- faceted-0.2.2.dist-info/licenses/LICENSE +21 -0
- faceted-0.2.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
"""Test suite for faceted module"""
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from itertools import product
|
|
5
|
+
|
|
6
|
+
import matplotlib.axes
|
|
7
|
+
import matplotlib.figure
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from ..faceted import (
|
|
13
|
+
_DEFAULT_ASPECT,
|
|
14
|
+
_DEFAULT_WIDTH,
|
|
15
|
+
faceted,
|
|
16
|
+
faceted_ax,
|
|
17
|
+
_infer_constraints,
|
|
18
|
+
_infer_grid_class,
|
|
19
|
+
HeightConstrainedAxesGrid,
|
|
20
|
+
HeightAndWidthConstrainedAxesGrid,
|
|
21
|
+
WidthConstrainedAxesGrid,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
plt.switch_backend("agg")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_TOP_PAD = _BOTTOM_PAD = _LEFT_PAD = _RIGHT_PAD = 0.25
|
|
29
|
+
_HORIZONTAL_INTERNAL_PAD = 0.25
|
|
30
|
+
_VERTICAL_INTERNAL_PAD = 0.5
|
|
31
|
+
_INTERNAL_PAD = (_HORIZONTAL_INTERNAL_PAD, _VERTICAL_INTERNAL_PAD)
|
|
32
|
+
_ASPECT_CONSTRAINT = 0.5
|
|
33
|
+
_HEIGHT_CONSTRAINT = 7.0
|
|
34
|
+
_WIDTH_CONSTRAINT = 8.0
|
|
35
|
+
_SHORT_SIDE_PAD = 0.25
|
|
36
|
+
_LONG_SIDE_PAD = 0.25
|
|
37
|
+
_CBAR_THICKNESS = 0.125
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_bounds(fig, ax):
|
|
41
|
+
return ax.bbox.transformed(fig.transFigure.inverted()).bounds
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_faceted_cbar_mode_none():
|
|
45
|
+
fig, axes = faceted(1, 2, width=_WIDTH_CONSTRAINT, aspect=_ASPECT_CONSTRAINT)
|
|
46
|
+
assert len(axes) == 2
|
|
47
|
+
plt.close(fig)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_faceted_cbar_mode_single():
|
|
51
|
+
fig, axes, cax = faceted(
|
|
52
|
+
1, 2, width=_WIDTH_CONSTRAINT, aspect=_ASPECT_CONSTRAINT, cbar_mode="single"
|
|
53
|
+
)
|
|
54
|
+
assert len(axes) == 2
|
|
55
|
+
plt.close(fig)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_faceted_cbar_mode_each():
|
|
59
|
+
fig, axes, caxes = faceted(
|
|
60
|
+
1, 2, width=_WIDTH_CONSTRAINT, aspect=_ASPECT_CONSTRAINT, cbar_mode="each"
|
|
61
|
+
)
|
|
62
|
+
assert len(axes) == 2
|
|
63
|
+
assert len(axes) == len(caxes)
|
|
64
|
+
plt.close(fig)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.parametrize(
|
|
68
|
+
("width", "height", "aspect"), [(1, 1, None), (1, None, 1), (None, 1, 1)]
|
|
69
|
+
)
|
|
70
|
+
def test_faceted_cbar_mode_invalid(width, height, aspect):
|
|
71
|
+
with pytest.raises(ValueError):
|
|
72
|
+
faceted(1, 2, width=width, height=height, aspect=aspect, cbar_mode="invalid")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_faceted_invalid_internal_pad():
|
|
76
|
+
with pytest.raises(ValueError):
|
|
77
|
+
faceted(
|
|
78
|
+
1,
|
|
79
|
+
2,
|
|
80
|
+
width=_WIDTH_CONSTRAINT,
|
|
81
|
+
aspect=_ASPECT_CONSTRAINT,
|
|
82
|
+
internal_pad=(1, 2, 3),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize(
|
|
87
|
+
("inputs", "expected"),
|
|
88
|
+
[
|
|
89
|
+
((None, None, None), (_DEFAULT_WIDTH, None, _DEFAULT_ASPECT)),
|
|
90
|
+
((3.0, None, None), (3.0, None, _DEFAULT_ASPECT)),
|
|
91
|
+
((None, 3.0, None), (None, 3.0, _DEFAULT_ASPECT)),
|
|
92
|
+
((None, None, 3.0), (_DEFAULT_WIDTH, None, 3.0)),
|
|
93
|
+
((3.0, 3.0, None), (3.0, 3.0, None)),
|
|
94
|
+
((None, 3.0, 3.0), (None, 3.0, 3.0)),
|
|
95
|
+
((3.0, None, 3.0), (3.0, None, 3.0)),
|
|
96
|
+
((3.0, 3.0, 3.0), ValueError),
|
|
97
|
+
],
|
|
98
|
+
ids=lambda x: str(x),
|
|
99
|
+
)
|
|
100
|
+
def test__infer_constraints(inputs, expected):
|
|
101
|
+
if not isinstance(expected, tuple) and issubclass(expected, Exception):
|
|
102
|
+
with pytest.raises(expected):
|
|
103
|
+
_infer_constraints(*inputs)
|
|
104
|
+
else:
|
|
105
|
+
result = _infer_constraints(*inputs)
|
|
106
|
+
assert result == expected
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.parametrize(
|
|
110
|
+
("width", "height", "aspect", "expected"),
|
|
111
|
+
[
|
|
112
|
+
(5.0, 5.0, None, HeightAndWidthConstrainedAxesGrid),
|
|
113
|
+
(5.0, None, 5.0, WidthConstrainedAxesGrid),
|
|
114
|
+
(None, 5.0, 5.0, HeightConstrainedAxesGrid),
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
def test__infer_grid_class(width, height, aspect, expected):
|
|
118
|
+
result = _infer_grid_class(width, height, aspect)
|
|
119
|
+
assert result == expected
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
_LAYOUTS = [(1, 1), (1, 2), (2, 1), (2, 2), (5, 3)]
|
|
123
|
+
_CBAR_MODES = [None, "single", "each", "edge"]
|
|
124
|
+
_CBAR_LOCATIONS = ["bottom", "right", "top", "left"]
|
|
125
|
+
_CONSTRAINTS = ["height-and-aspect", "width-and-aspect", "height-and-width"]
|
|
126
|
+
_CG_LAYOUTS = product(_CBAR_MODES, _CBAR_LOCATIONS, _LAYOUTS, _CONSTRAINTS)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def format_layout(layout):
|
|
130
|
+
cbar_mode, cbar_loc, (rows, cols), constraint = layout
|
|
131
|
+
return "cbar_mode={!r}, cbar_location={!r}, rows={}, cols={}, constraint={}".format(
|
|
132
|
+
cbar_mode, cbar_loc, rows, cols, constraint
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
_CG_IDS = OrderedDict([(layout, format_layout(layout)) for layout in _CG_LAYOUTS])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.fixture(params=_CG_IDS.keys(), ids=_CG_IDS.values())
|
|
140
|
+
def grid(request):
|
|
141
|
+
mode, location, (rows, cols), constraint = request.param
|
|
142
|
+
if constraint == "width-and-aspect":
|
|
143
|
+
obj = WidthConstrainedAxesGrid(
|
|
144
|
+
rows,
|
|
145
|
+
cols,
|
|
146
|
+
width=_WIDTH_CONSTRAINT,
|
|
147
|
+
aspect=_ASPECT_CONSTRAINT,
|
|
148
|
+
top_pad=_TOP_PAD,
|
|
149
|
+
bottom_pad=_BOTTOM_PAD,
|
|
150
|
+
left_pad=_LEFT_PAD,
|
|
151
|
+
right_pad=_RIGHT_PAD,
|
|
152
|
+
cbar_mode=mode,
|
|
153
|
+
cbar_pad=_LONG_SIDE_PAD,
|
|
154
|
+
axes_pad=_INTERNAL_PAD,
|
|
155
|
+
cbar_location=location,
|
|
156
|
+
cbar_size=_CBAR_THICKNESS,
|
|
157
|
+
cbar_short_side_pad=_SHORT_SIDE_PAD,
|
|
158
|
+
)
|
|
159
|
+
elif constraint == "height-and-aspect":
|
|
160
|
+
obj = HeightConstrainedAxesGrid(
|
|
161
|
+
rows,
|
|
162
|
+
cols,
|
|
163
|
+
height=_HEIGHT_CONSTRAINT,
|
|
164
|
+
aspect=_ASPECT_CONSTRAINT,
|
|
165
|
+
top_pad=_TOP_PAD,
|
|
166
|
+
bottom_pad=_BOTTOM_PAD,
|
|
167
|
+
left_pad=_LEFT_PAD,
|
|
168
|
+
right_pad=_RIGHT_PAD,
|
|
169
|
+
cbar_mode=mode,
|
|
170
|
+
cbar_pad=_LONG_SIDE_PAD,
|
|
171
|
+
axes_pad=_INTERNAL_PAD,
|
|
172
|
+
cbar_location=location,
|
|
173
|
+
cbar_size=_CBAR_THICKNESS,
|
|
174
|
+
cbar_short_side_pad=_SHORT_SIDE_PAD,
|
|
175
|
+
)
|
|
176
|
+
elif constraint == "height-and-width":
|
|
177
|
+
obj = HeightAndWidthConstrainedAxesGrid(
|
|
178
|
+
rows,
|
|
179
|
+
cols,
|
|
180
|
+
height=_HEIGHT_CONSTRAINT,
|
|
181
|
+
width=_WIDTH_CONSTRAINT,
|
|
182
|
+
top_pad=_TOP_PAD,
|
|
183
|
+
bottom_pad=_BOTTOM_PAD,
|
|
184
|
+
left_pad=_LEFT_PAD,
|
|
185
|
+
right_pad=_RIGHT_PAD,
|
|
186
|
+
cbar_mode=mode,
|
|
187
|
+
cbar_pad=_LONG_SIDE_PAD,
|
|
188
|
+
axes_pad=_INTERNAL_PAD,
|
|
189
|
+
cbar_location=location,
|
|
190
|
+
cbar_size=_CBAR_THICKNESS,
|
|
191
|
+
cbar_short_side_pad=_SHORT_SIDE_PAD,
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
raise NotImplementedError()
|
|
195
|
+
yield obj
|
|
196
|
+
plt.close(obj.fig)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def get_tile_width(grid, left_pad=_LEFT_PAD, right_pad=_RIGHT_PAD):
|
|
200
|
+
return (
|
|
201
|
+
grid.width - left_pad - right_pad - (grid.cols - 1) * _HORIZONTAL_INTERNAL_PAD
|
|
202
|
+
) / grid.cols
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_tile_height(grid, bottom_pad=_BOTTOM_PAD, top_pad=_TOP_PAD):
|
|
206
|
+
return (
|
|
207
|
+
grid.height - bottom_pad - top_pad - (grid.rows - 1) * _VERTICAL_INTERNAL_PAD
|
|
208
|
+
) / grid.rows
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_constrained_axes_positions(grid):
|
|
212
|
+
if grid.cbar_mode == "each":
|
|
213
|
+
check_constrained_axes_positions_each(grid)
|
|
214
|
+
elif grid.cbar_mode == "single":
|
|
215
|
+
check_constrained_axes_positions_single(grid)
|
|
216
|
+
elif grid.cbar_mode == "edge":
|
|
217
|
+
check_constrained_axes_positions_edge(grid)
|
|
218
|
+
elif grid.cbar_mode is None:
|
|
219
|
+
check_constrained_axes_positions_none(grid)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_constrained_caxes_positions(grid):
|
|
223
|
+
if grid.cbar_mode == "each":
|
|
224
|
+
check_constrained_caxes_positions_each(grid)
|
|
225
|
+
elif grid.cbar_mode == "single":
|
|
226
|
+
check_constrained_caxes_positions_single(grid)
|
|
227
|
+
elif grid.cbar_mode == "edge":
|
|
228
|
+
check_constrained_caxes_positions_edge(grid)
|
|
229
|
+
elif grid.cbar_mode is None:
|
|
230
|
+
pytest.skip("Skipping colorbar positions test, because cbar_mode=None")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_plot_aspect(grid):
|
|
234
|
+
fig = grid.fig
|
|
235
|
+
width, height = fig.get_size_inches()
|
|
236
|
+
for ax in grid.axes:
|
|
237
|
+
ax_bounds = get_bounds(fig, ax)
|
|
238
|
+
_, _, _plot_width, _plot_height = ax_bounds
|
|
239
|
+
plot_width = _plot_width * width
|
|
240
|
+
plot_height = _plot_height * height
|
|
241
|
+
expected = grid.aspect
|
|
242
|
+
result = plot_height / plot_width
|
|
243
|
+
np.testing.assert_allclose(result, expected)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def check_constrained_axes_positions_none(grid):
|
|
247
|
+
rows, cols = grid.rows, grid.cols
|
|
248
|
+
width, height = grid.width, grid.height
|
|
249
|
+
tile_width, tile_height = get_tile_width(grid), get_tile_height(grid)
|
|
250
|
+
fig = grid.fig
|
|
251
|
+
|
|
252
|
+
indexes = list(product(range(rows - 1, -1, -1), range(cols)))
|
|
253
|
+
for ax, (row, col) in zip(grid.axes, indexes):
|
|
254
|
+
ax_bounds = get_bounds(fig, ax)
|
|
255
|
+
x0 = (_LEFT_PAD + col * (_HORIZONTAL_INTERNAL_PAD + tile_width)) / width
|
|
256
|
+
y0 = (_BOTTOM_PAD + row * (_VERTICAL_INTERNAL_PAD + tile_height)) / height
|
|
257
|
+
dx = tile_width / width
|
|
258
|
+
dy = tile_height / height
|
|
259
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
260
|
+
np.testing.assert_allclose(ax_bounds, expected_bounds)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def check_constrained_axes_positions_single(grid):
|
|
264
|
+
rows, cols = grid.rows, grid.cols
|
|
265
|
+
width, height = grid.width, grid.height
|
|
266
|
+
cbar_location = grid.cbar_location
|
|
267
|
+
fig = grid.fig
|
|
268
|
+
|
|
269
|
+
left_pad, right_pad = _LEFT_PAD, _RIGHT_PAD
|
|
270
|
+
bottom_pad, top_pad = _BOTTOM_PAD, _TOP_PAD
|
|
271
|
+
if cbar_location == "left":
|
|
272
|
+
left_pad = _LEFT_PAD + _CBAR_THICKNESS + _LONG_SIDE_PAD
|
|
273
|
+
elif cbar_location == "right":
|
|
274
|
+
right_pad = _RIGHT_PAD + _CBAR_THICKNESS + _LONG_SIDE_PAD
|
|
275
|
+
elif cbar_location == "bottom":
|
|
276
|
+
bottom_pad = _BOTTOM_PAD + _CBAR_THICKNESS + _LONG_SIDE_PAD
|
|
277
|
+
elif cbar_location == "top":
|
|
278
|
+
top_pad = _TOP_PAD + _CBAR_THICKNESS + _LONG_SIDE_PAD
|
|
279
|
+
|
|
280
|
+
tile_width = get_tile_width(grid, left_pad=left_pad, right_pad=right_pad)
|
|
281
|
+
tile_height = get_tile_height(grid, bottom_pad=bottom_pad, top_pad=top_pad)
|
|
282
|
+
|
|
283
|
+
indexes = list(product(range(rows - 1, -1, -1), range(cols)))
|
|
284
|
+
axes = grid.axes
|
|
285
|
+
for ax, (row, col) in zip(axes, indexes):
|
|
286
|
+
ax_bounds = get_bounds(fig, ax)
|
|
287
|
+
x0 = (left_pad + col * (_HORIZONTAL_INTERNAL_PAD + tile_width)) / width
|
|
288
|
+
y0 = (bottom_pad + row * (_VERTICAL_INTERNAL_PAD + tile_height)) / height
|
|
289
|
+
dx = tile_width / width
|
|
290
|
+
dy = tile_height / height
|
|
291
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
292
|
+
np.testing.assert_allclose(ax_bounds, expected_bounds)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def check_constrained_caxes_positions_single(grid):
|
|
296
|
+
width, height = grid.width, grid.height
|
|
297
|
+
cbar_location = grid.cbar_location
|
|
298
|
+
fig = grid.fig
|
|
299
|
+
|
|
300
|
+
cax = grid.caxes
|
|
301
|
+
cax_bounds = get_bounds(fig, cax)
|
|
302
|
+
if cbar_location == "bottom":
|
|
303
|
+
x0 = (_LEFT_PAD + _SHORT_SIDE_PAD) / width
|
|
304
|
+
y0 = _BOTTOM_PAD / height
|
|
305
|
+
dx = (width - _LEFT_PAD - _RIGHT_PAD - 2.0 * _SHORT_SIDE_PAD) / width
|
|
306
|
+
dy = _CBAR_THICKNESS / height
|
|
307
|
+
elif cbar_location == "right":
|
|
308
|
+
x0 = (width - _CBAR_THICKNESS - _RIGHT_PAD) / width
|
|
309
|
+
y0 = (_BOTTOM_PAD + _SHORT_SIDE_PAD) / height
|
|
310
|
+
dx = _CBAR_THICKNESS / width
|
|
311
|
+
dy = (height - _TOP_PAD - _BOTTOM_PAD - 2.0 * _SHORT_SIDE_PAD) / height
|
|
312
|
+
elif cbar_location == "top":
|
|
313
|
+
x0 = (_LEFT_PAD + _SHORT_SIDE_PAD) / width
|
|
314
|
+
y0 = (height - _CBAR_THICKNESS - _TOP_PAD) / height
|
|
315
|
+
dx = (width - _LEFT_PAD - _RIGHT_PAD - 2.0 * _SHORT_SIDE_PAD) / width
|
|
316
|
+
dy = _CBAR_THICKNESS / height
|
|
317
|
+
elif cbar_location == "left":
|
|
318
|
+
x0 = _LEFT_PAD / width
|
|
319
|
+
y0 = (_BOTTOM_PAD + _SHORT_SIDE_PAD) / height
|
|
320
|
+
dx = _CBAR_THICKNESS / width
|
|
321
|
+
dy = (height - _TOP_PAD - _BOTTOM_PAD - 2.0 * _SHORT_SIDE_PAD) / height
|
|
322
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
323
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def check_constrained_axes_positions_each(grid):
|
|
327
|
+
rows, cols = grid.rows, grid.cols
|
|
328
|
+
width, height = grid.width, grid.height
|
|
329
|
+
tile_width, tile_height = get_tile_width(grid), get_tile_height(grid)
|
|
330
|
+
cbar_location = grid.cbar_location
|
|
331
|
+
fig = grid.fig
|
|
332
|
+
|
|
333
|
+
indexes = list(product(range(rows - 1, -1, -1), range(cols)))
|
|
334
|
+
axes = grid.axes
|
|
335
|
+
if cbar_location == "bottom":
|
|
336
|
+
for ax, (row, col) in zip(axes, indexes):
|
|
337
|
+
ax_bounds = get_bounds(fig, ax)
|
|
338
|
+
x0 = (_LEFT_PAD + col * (tile_width + _HORIZONTAL_INTERNAL_PAD)) / width
|
|
339
|
+
y0 = (
|
|
340
|
+
_BOTTOM_PAD
|
|
341
|
+
+ _CBAR_THICKNESS
|
|
342
|
+
+ _LONG_SIDE_PAD
|
|
343
|
+
+ row * (tile_height + _VERTICAL_INTERNAL_PAD)
|
|
344
|
+
) / height
|
|
345
|
+
dx = tile_width / width
|
|
346
|
+
dy = (tile_height - _CBAR_THICKNESS - _LONG_SIDE_PAD) / height
|
|
347
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
348
|
+
np.testing.assert_allclose(ax_bounds, expected_bounds)
|
|
349
|
+
elif cbar_location == "top":
|
|
350
|
+
for ax, (row, col) in zip(axes, indexes):
|
|
351
|
+
ax_bounds = get_bounds(fig, ax)
|
|
352
|
+
x0 = (_LEFT_PAD + col * (_HORIZONTAL_INTERNAL_PAD + tile_width)) / width
|
|
353
|
+
y0 = (_BOTTOM_PAD + row * (_VERTICAL_INTERNAL_PAD + tile_height)) / height
|
|
354
|
+
dx = tile_width / width
|
|
355
|
+
dy = (tile_height - _CBAR_THICKNESS - _LONG_SIDE_PAD) / height
|
|
356
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
357
|
+
np.testing.assert_allclose(ax_bounds, expected_bounds)
|
|
358
|
+
elif cbar_location == "right":
|
|
359
|
+
for ax, (row, col) in zip(axes, indexes):
|
|
360
|
+
ax_bounds = get_bounds(fig, ax)
|
|
361
|
+
x0 = (_LEFT_PAD + col * (_HORIZONTAL_INTERNAL_PAD + tile_width)) / width
|
|
362
|
+
y0 = (_BOTTOM_PAD + row * (_VERTICAL_INTERNAL_PAD + tile_height)) / height
|
|
363
|
+
dx = (tile_width - _CBAR_THICKNESS - _LONG_SIDE_PAD) / width
|
|
364
|
+
dy = tile_height / height
|
|
365
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
366
|
+
np.testing.assert_allclose(ax_bounds, expected_bounds)
|
|
367
|
+
elif cbar_location == "left":
|
|
368
|
+
for ax, (row, col) in zip(axes, indexes):
|
|
369
|
+
ax_bounds = get_bounds(fig, ax)
|
|
370
|
+
x0 = (
|
|
371
|
+
_LEFT_PAD
|
|
372
|
+
+ _CBAR_THICKNESS
|
|
373
|
+
+ _LONG_SIDE_PAD
|
|
374
|
+
+ col * (_HORIZONTAL_INTERNAL_PAD + tile_width)
|
|
375
|
+
) / width
|
|
376
|
+
y0 = (_BOTTOM_PAD + row * (_VERTICAL_INTERNAL_PAD + tile_height)) / height
|
|
377
|
+
dx = (tile_width - _CBAR_THICKNESS - _LONG_SIDE_PAD) / width
|
|
378
|
+
dy = tile_height / height
|
|
379
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
380
|
+
np.testing.assert_allclose(ax_bounds, expected_bounds)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def check_constrained_caxes_positions_each(grid):
|
|
384
|
+
rows, cols = grid.rows, grid.cols
|
|
385
|
+
width, height = grid.width, grid.height
|
|
386
|
+
tile_width, tile_height = get_tile_width(grid), get_tile_height(grid)
|
|
387
|
+
cbar_location = grid.cbar_location
|
|
388
|
+
fig = grid.fig
|
|
389
|
+
|
|
390
|
+
indexes = list(product(range(rows - 1, -1, -1), range(cols)))
|
|
391
|
+
caxes = grid.caxes
|
|
392
|
+
if cbar_location == "bottom":
|
|
393
|
+
for cax, (row, col) in zip(caxes, indexes):
|
|
394
|
+
cax_bounds = get_bounds(fig, cax)
|
|
395
|
+
x0 = (
|
|
396
|
+
_LEFT_PAD
|
|
397
|
+
+ col * (_HORIZONTAL_INTERNAL_PAD + tile_width)
|
|
398
|
+
+ _SHORT_SIDE_PAD
|
|
399
|
+
) / width
|
|
400
|
+
y0 = (_BOTTOM_PAD + row * (_VERTICAL_INTERNAL_PAD + tile_height)) / height
|
|
401
|
+
dx = (tile_width - 2.0 * _SHORT_SIDE_PAD) / width
|
|
402
|
+
dy = _CBAR_THICKNESS / height
|
|
403
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
404
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
405
|
+
elif cbar_location == "top":
|
|
406
|
+
for cax, (row, col) in zip(caxes, indexes):
|
|
407
|
+
cax_bounds = get_bounds(fig, cax)
|
|
408
|
+
x0 = (
|
|
409
|
+
_LEFT_PAD
|
|
410
|
+
+ col * (_HORIZONTAL_INTERNAL_PAD + tile_width)
|
|
411
|
+
+ _SHORT_SIDE_PAD
|
|
412
|
+
) / width
|
|
413
|
+
y0 = (
|
|
414
|
+
_BOTTOM_PAD
|
|
415
|
+
+ row * (_VERTICAL_INTERNAL_PAD + tile_height)
|
|
416
|
+
+ tile_height
|
|
417
|
+
- _CBAR_THICKNESS
|
|
418
|
+
) / height
|
|
419
|
+
dx = (tile_width - 2.0 * _SHORT_SIDE_PAD) / width
|
|
420
|
+
dy = _CBAR_THICKNESS / height
|
|
421
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
422
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
423
|
+
elif cbar_location == "right":
|
|
424
|
+
for cax, (row, col) in zip(caxes, indexes):
|
|
425
|
+
cax_bounds = get_bounds(fig, cax)
|
|
426
|
+
x0 = (
|
|
427
|
+
_LEFT_PAD
|
|
428
|
+
+ col * (_HORIZONTAL_INTERNAL_PAD + tile_width)
|
|
429
|
+
+ tile_width
|
|
430
|
+
- _CBAR_THICKNESS
|
|
431
|
+
) / width
|
|
432
|
+
y0 = (
|
|
433
|
+
_BOTTOM_PAD
|
|
434
|
+
+ row * (_VERTICAL_INTERNAL_PAD + tile_height)
|
|
435
|
+
+ _SHORT_SIDE_PAD
|
|
436
|
+
) / height
|
|
437
|
+
dx = _CBAR_THICKNESS / width
|
|
438
|
+
dy = (tile_height - 2.0 * _SHORT_SIDE_PAD) / height
|
|
439
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
440
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
441
|
+
elif cbar_location == "left":
|
|
442
|
+
for cax, (row, col) in zip(caxes, indexes):
|
|
443
|
+
cax_bounds = get_bounds(fig, cax)
|
|
444
|
+
x0 = (_LEFT_PAD + col * (_HORIZONTAL_INTERNAL_PAD + tile_width)) / width
|
|
445
|
+
y0 = (
|
|
446
|
+
_BOTTOM_PAD
|
|
447
|
+
+ row * (_VERTICAL_INTERNAL_PAD + tile_height)
|
|
448
|
+
+ _SHORT_SIDE_PAD
|
|
449
|
+
) / height
|
|
450
|
+
dx = _CBAR_THICKNESS / width
|
|
451
|
+
dy = (tile_height - 2.0 * _SHORT_SIDE_PAD) / height
|
|
452
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
453
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def check_constrained_axes_positions_edge(grid):
|
|
457
|
+
# The positions of the axes are the same as for cbar_mode='single'
|
|
458
|
+
check_constrained_axes_positions_single(grid)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def check_constrained_caxes_positions_edge(grid):
|
|
462
|
+
rows, cols = grid.rows, grid.cols
|
|
463
|
+
width, height = grid.width, grid.height
|
|
464
|
+
tile_width, tile_height = get_tile_width(grid), get_tile_height(grid)
|
|
465
|
+
cbar_location = grid.cbar_location
|
|
466
|
+
fig = grid.fig
|
|
467
|
+
|
|
468
|
+
caxes = grid.caxes
|
|
469
|
+
if cbar_location == "bottom":
|
|
470
|
+
for col, cax in zip(range(cols), caxes):
|
|
471
|
+
cax_bounds = get_bounds(fig, cax)
|
|
472
|
+
x0 = (
|
|
473
|
+
_LEFT_PAD
|
|
474
|
+
+ col * (_HORIZONTAL_INTERNAL_PAD + tile_width)
|
|
475
|
+
+ _SHORT_SIDE_PAD
|
|
476
|
+
) / width
|
|
477
|
+
y0 = _BOTTOM_PAD / height
|
|
478
|
+
dx = (tile_width - 2.0 * _SHORT_SIDE_PAD) / width
|
|
479
|
+
dy = _CBAR_THICKNESS / height
|
|
480
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
481
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
482
|
+
elif cbar_location == "top":
|
|
483
|
+
for col, cax in zip(range(cols), caxes):
|
|
484
|
+
cax_bounds = get_bounds(fig, cax)
|
|
485
|
+
x0 = (
|
|
486
|
+
_LEFT_PAD
|
|
487
|
+
+ col * (_HORIZONTAL_INTERNAL_PAD + tile_width)
|
|
488
|
+
+ _SHORT_SIDE_PAD
|
|
489
|
+
) / width
|
|
490
|
+
y0 = (height - _CBAR_THICKNESS - _TOP_PAD) / height
|
|
491
|
+
dx = (tile_width - 2.0 * _SHORT_SIDE_PAD) / width
|
|
492
|
+
dy = _CBAR_THICKNESS / height
|
|
493
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
494
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
495
|
+
elif cbar_location == "right":
|
|
496
|
+
for row, cax in zip(range(rows - 1, -1, -1), caxes):
|
|
497
|
+
cax_bounds = get_bounds(fig, cax)
|
|
498
|
+
x0 = (width - _CBAR_THICKNESS - _RIGHT_PAD) / width
|
|
499
|
+
y0 = (
|
|
500
|
+
_BOTTOM_PAD
|
|
501
|
+
+ row * (_VERTICAL_INTERNAL_PAD + tile_height)
|
|
502
|
+
+ _SHORT_SIDE_PAD
|
|
503
|
+
) / height
|
|
504
|
+
dx = _CBAR_THICKNESS / width
|
|
505
|
+
dy = (tile_height - 2.0 * _SHORT_SIDE_PAD) / height
|
|
506
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
507
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
508
|
+
elif cbar_location == "left":
|
|
509
|
+
for row, cax in zip(range(rows - 1, -1, -1), caxes):
|
|
510
|
+
cax_bounds = get_bounds(fig, cax)
|
|
511
|
+
x0 = _LEFT_PAD / width
|
|
512
|
+
y0 = (
|
|
513
|
+
_BOTTOM_PAD
|
|
514
|
+
+ row * (_VERTICAL_INTERNAL_PAD + tile_height)
|
|
515
|
+
+ _SHORT_SIDE_PAD
|
|
516
|
+
) / height
|
|
517
|
+
dx = _CBAR_THICKNESS / width
|
|
518
|
+
dy = (tile_height - 2.0 * _SHORT_SIDE_PAD) / height
|
|
519
|
+
expected_bounds = [x0, y0, dx, dy]
|
|
520
|
+
np.testing.assert_allclose(cax_bounds, expected_bounds)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def shared_grid(sharex, sharey):
|
|
524
|
+
return WidthConstrainedAxesGrid(
|
|
525
|
+
2,
|
|
526
|
+
2,
|
|
527
|
+
width=_WIDTH_CONSTRAINT,
|
|
528
|
+
aspect=_ASPECT_CONSTRAINT,
|
|
529
|
+
sharex=sharex,
|
|
530
|
+
sharey=sharey,
|
|
531
|
+
cbar_mode="single",
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def assert_visible_xticklabels(ax):
|
|
536
|
+
assert ax.xaxis._get_tick(ax.xaxis.major).label1.get_visible()
|
|
537
|
+
assert ax.xaxis._get_tick(ax.xaxis.minor).label1.get_visible()
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def assert_invisible_xticklabels(ax):
|
|
541
|
+
assert not ax.xaxis._get_tick(ax.xaxis.major).label1.get_visible()
|
|
542
|
+
assert not ax.xaxis._get_tick(ax.xaxis.minor).label1.get_visible()
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def assert_visible_yticklabels(ax):
|
|
546
|
+
assert ax.yaxis._get_tick(ax.yaxis.major).label1.get_visible()
|
|
547
|
+
assert ax.yaxis._get_tick(ax.yaxis.minor).label1.get_visible()
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def assert_invisible_yticklabels(ax):
|
|
551
|
+
assert not ax.yaxis._get_tick(ax.yaxis.major).label1.get_visible()
|
|
552
|
+
assert not ax.yaxis._get_tick(ax.yaxis.minor).label1.get_visible()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def assert_valid_x_sharing(shared_grid, sharex):
|
|
556
|
+
axes = np.reshape(shared_grid.axes, (shared_grid.rows, shared_grid.cols))
|
|
557
|
+
if sharex in ["all", True]:
|
|
558
|
+
ax_ref = axes.flatten()[0]
|
|
559
|
+
for ax in axes.flatten():
|
|
560
|
+
assert ax.xaxis.major == ax_ref.xaxis.major
|
|
561
|
+
assert ax.xaxis.minor == ax_ref.xaxis.minor
|
|
562
|
+
for ax in axes[:-1, :].flatten():
|
|
563
|
+
assert_invisible_xticklabels(ax)
|
|
564
|
+
for ax in axes[-1, :].flatten():
|
|
565
|
+
assert_visible_xticklabels(ax)
|
|
566
|
+
elif sharex == "row":
|
|
567
|
+
for row in axes:
|
|
568
|
+
ax_ref = row[0]
|
|
569
|
+
for ax in row:
|
|
570
|
+
assert ax.xaxis.major == ax_ref.xaxis.major
|
|
571
|
+
assert ax.xaxis.minor == ax_ref.xaxis.minor
|
|
572
|
+
for col in axes.T:
|
|
573
|
+
ax_ref = col[0]
|
|
574
|
+
for ax in col[1:]:
|
|
575
|
+
assert ax.xaxis.major != ax_ref.xaxis.major
|
|
576
|
+
assert ax.xaxis.minor != ax_ref.xaxis.minor
|
|
577
|
+
for ax in axes.flatten():
|
|
578
|
+
assert_visible_xticklabels(ax)
|
|
579
|
+
elif sharex == "col":
|
|
580
|
+
for col in axes.T:
|
|
581
|
+
ax_ref = col[0]
|
|
582
|
+
for ax in col:
|
|
583
|
+
assert ax.xaxis.major == ax_ref.xaxis.major
|
|
584
|
+
assert ax.xaxis.minor == ax_ref.xaxis.minor
|
|
585
|
+
for row in axes:
|
|
586
|
+
ax_ref = row[0]
|
|
587
|
+
for ax in row[1:]:
|
|
588
|
+
assert ax.xaxis.major != ax_ref.xaxis.major
|
|
589
|
+
assert ax.xaxis.minor != ax_ref.xaxis.minor
|
|
590
|
+
for ax in axes[:-1, :].flatten():
|
|
591
|
+
assert_invisible_xticklabels(ax)
|
|
592
|
+
for ax in axes[-1, :].flatten():
|
|
593
|
+
assert_visible_xticklabels(ax)
|
|
594
|
+
elif sharex in ["none", False]:
|
|
595
|
+
ax_ref = axes.flatten()[0]
|
|
596
|
+
for ax in axes.flatten()[1:]:
|
|
597
|
+
assert ax.xaxis.major != ax_ref.xaxis.major
|
|
598
|
+
assert ax.xaxis.minor != ax_ref.xaxis.minor
|
|
599
|
+
for ax in axes.flatten():
|
|
600
|
+
assert_visible_xticklabels(ax)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def assert_valid_y_sharing(shared_grid, sharey):
|
|
604
|
+
axes = np.reshape(shared_grid.axes, (shared_grid.rows, shared_grid.cols))
|
|
605
|
+
if sharey in ["all", True]:
|
|
606
|
+
ax_ref = axes.flatten()[0]
|
|
607
|
+
for ax in axes.flatten():
|
|
608
|
+
assert ax.yaxis.major == ax_ref.yaxis.major
|
|
609
|
+
assert ax.yaxis.minor == ax_ref.yaxis.minor
|
|
610
|
+
for ax in axes[:, 1:].flatten():
|
|
611
|
+
assert_invisible_yticklabels(ax)
|
|
612
|
+
for ax in axes[:, 0].flatten():
|
|
613
|
+
assert_visible_yticklabels(ax)
|
|
614
|
+
elif sharey == "row":
|
|
615
|
+
for row in axes:
|
|
616
|
+
ax_ref = row[0]
|
|
617
|
+
for ax in row:
|
|
618
|
+
assert ax.yaxis.major == ax_ref.yaxis.major
|
|
619
|
+
assert ax.yaxis.minor == ax_ref.yaxis.minor
|
|
620
|
+
for col in axes.T:
|
|
621
|
+
ax_ref = col[0]
|
|
622
|
+
for ax in col[1:]:
|
|
623
|
+
assert ax.yaxis.major != ax_ref.yaxis.major
|
|
624
|
+
assert ax.yaxis.minor != ax_ref.yaxis.minor
|
|
625
|
+
for ax in axes[:, 1:].flatten():
|
|
626
|
+
assert_invisible_yticklabels(ax)
|
|
627
|
+
for ax in axes[:, 0].flatten():
|
|
628
|
+
assert_visible_yticklabels(ax)
|
|
629
|
+
elif sharey == "col":
|
|
630
|
+
for col in axes.T:
|
|
631
|
+
ax_ref = col[0]
|
|
632
|
+
for ax in col:
|
|
633
|
+
assert ax.yaxis.major == ax_ref.yaxis.major
|
|
634
|
+
assert ax.yaxis.minor == ax_ref.yaxis.minor
|
|
635
|
+
for row in axes:
|
|
636
|
+
ax_ref = row[0]
|
|
637
|
+
for ax in row[1:]:
|
|
638
|
+
assert ax.yaxis.major != ax_ref.yaxis.major
|
|
639
|
+
assert ax.yaxis.minor != ax_ref.yaxis.minor
|
|
640
|
+
for ax in axes.flatten():
|
|
641
|
+
assert_visible_yticklabels(ax)
|
|
642
|
+
elif sharey in ["none", False]:
|
|
643
|
+
ax_ref = axes.flatten()[0]
|
|
644
|
+
for ax in axes.flatten()[1:]:
|
|
645
|
+
assert ax.yaxis.major != ax_ref.yaxis.major
|
|
646
|
+
assert ax.yaxis.minor != ax_ref.yaxis.minor
|
|
647
|
+
for ax in axes.flatten():
|
|
648
|
+
assert_visible_yticklabels(ax)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
_SHARE_OPTIONS = ["all", "row", "col", "none", True, False]
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
@pytest.mark.parametrize(("sharex", "sharey"), product(_SHARE_OPTIONS, _SHARE_OPTIONS))
|
|
655
|
+
def test_share_axes_mixin(sharex, sharey):
|
|
656
|
+
grid = shared_grid(sharex, sharey)
|
|
657
|
+
assert_valid_x_sharing(grid, sharex)
|
|
658
|
+
assert_valid_y_sharing(grid, sharey)
|
|
659
|
+
plt.close(grid.fig)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def test_cartopy():
|
|
663
|
+
pytest.importorskip("cartopy")
|
|
664
|
+
import cartopy.crs as ccrs
|
|
665
|
+
from cartopy.mpl.geoaxes import GeoAxes
|
|
666
|
+
|
|
667
|
+
fig, axes = faceted(
|
|
668
|
+
2,
|
|
669
|
+
2,
|
|
670
|
+
width=_WIDTH_CONSTRAINT,
|
|
671
|
+
aspect=_ASPECT_CONSTRAINT,
|
|
672
|
+
axes_kwargs={"projection": ccrs.PlateCarree()},
|
|
673
|
+
)
|
|
674
|
+
for ax in axes:
|
|
675
|
+
assert isinstance(ax, GeoAxes)
|
|
676
|
+
plt.close(fig)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
@pytest.mark.parametrize(
|
|
680
|
+
("cbar_mode", "cbar_expected"),
|
|
681
|
+
[(None, False), ("single", True), ("edge", True), ("each", True)],
|
|
682
|
+
)
|
|
683
|
+
def test_faceted_ax(cbar_mode, cbar_expected):
|
|
684
|
+
if cbar_expected:
|
|
685
|
+
fig, ax, cax = faceted_ax(
|
|
686
|
+
cbar_mode=cbar_mode, width=_WIDTH_CONSTRAINT, aspect=_ASPECT_CONSTRAINT
|
|
687
|
+
)
|
|
688
|
+
assert isinstance(fig, matplotlib.figure.Figure)
|
|
689
|
+
assert isinstance(ax, matplotlib.axes.Axes)
|
|
690
|
+
assert isinstance(cax, matplotlib.axes.Axes)
|
|
691
|
+
else:
|
|
692
|
+
fig, ax = faceted_ax(
|
|
693
|
+
cbar_mode=cbar_mode, width=_WIDTH_CONSTRAINT, aspect=_ASPECT_CONSTRAINT
|
|
694
|
+
)
|
|
695
|
+
assert isinstance(fig, matplotlib.figure.Figure)
|
|
696
|
+
assert isinstance(ax, matplotlib.axes.Axes)
|