EasyPlotLib 0.2.6__tar.gz → 0.2.7__tar.gz
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.
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib.egg-info/PKG-INFO +1 -1
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib.egg-info/SOURCES.txt +1 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/PKG-INFO +1 -1
- easyplotlib-0.2.7/tests/test_map_aspect_colorbar.py +157 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/__init__.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/cartopy_helper.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/export.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/figsizes_set.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreadventor-bold.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreadventor-bolditalic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreadventor-italic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreadventor-regular.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyrechorus-mediumitalic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyrecursor-bold.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyrecursor-bolditalic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyrecursor-italic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyrecursor-regular.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreheros-bold.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreheros-bolditalic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreheros-italic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreheros-regular.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreschola-bold.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreschola-bolditalic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreschola-italic.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/fonts/texgyreschola-regular.ttf +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/helpers.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/palettes.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib/styles/nature.mplstyle +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib.egg-info/dependency_links.txt +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/EasyPlotLib.egg-info/top_level.txt +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/README.md +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/setup.cfg +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/setup.py +0 -0
- {easyplotlib-0.2.6 → easyplotlib-0.2.7}/tests/test_subplot_labels.py +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Map aspect ratio vs colourbar height — lesson + regression check.
|
|
3
|
+
|
|
4
|
+
THE LESSON (learned the hard way on a WRF d01/d02 + land-use + irrigation row):
|
|
5
|
+
|
|
6
|
+
* A geographic panel is almost never 1:1. With ``ax.set_aspect("equal")`` (every
|
|
7
|
+
map: cartopy, salem, or a plain ``imshow`` of a lon/lat field) the AXES BOX
|
|
8
|
+
shrinks to the data's width/height ratio. Δlon and Δlat differ (and projection
|
|
9
|
+
distorts further), so the drawn map is shorter (or narrower) than the grid cell
|
|
10
|
+
matplotlib allocated for it.
|
|
11
|
+
|
|
12
|
+
* ``fig.colorbar(mappable, ax=ax)`` sizes the colourbar to the CELL, not to the
|
|
13
|
+
shrunk map. Result: the colourbar stands taller than the map it annotates — even
|
|
14
|
+
though nothing "overlaps". This is the bug this test reproduces.
|
|
15
|
+
|
|
16
|
+
* In a single ROW of maps with DIFFERENT aspects (e.g. a wide parent domain next to
|
|
17
|
+
square nests), equal-width cells make the wide panel short and the square panels
|
|
18
|
+
tall → the row looks ragged.
|
|
19
|
+
|
|
20
|
+
THE FIXES (both demonstrated and asserted below):
|
|
21
|
+
|
|
22
|
+
A. EQUAL PANEL HEIGHTS: give each map a column ``width_ratio`` proportional to its
|
|
23
|
+
own aspect (= width/height). Then every cell matches its map's aspect and all
|
|
24
|
+
maps render at the same height.
|
|
25
|
+
|
|
26
|
+
B. COLOURBAR = MAP HEIGHT: let the layout engine place the colourbars (so they sit
|
|
27
|
+
to the right with no overlap), then ``fig.canvas.draw()``, FREEZE the layout
|
|
28
|
+
(``fig.set_layout_engine("none")``) and clamp each colourbar axes' ``y0``/height
|
|
29
|
+
to its map's drawn ``get_position()``. Only the height/bottom change, so the
|
|
30
|
+
horizontal placement the engine chose is preserved.
|
|
31
|
+
|
|
32
|
+
Run: python tests/test_map_aspect_colorbar.py
|
|
33
|
+
Produces tests/test_map_aspect_colorbar_before.png and _after.png for eyeballing,
|
|
34
|
+
and asserts the colourbar/map height ratio is ~1.7 before and ~1.0 after.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import os
|
|
38
|
+
|
|
39
|
+
import numpy as np
|
|
40
|
+
import matplotlib
|
|
41
|
+
matplotlib.use("Agg")
|
|
42
|
+
import matplotlib.pyplot as plt
|
|
43
|
+
from matplotlib.cm import ScalarMappable
|
|
44
|
+
from matplotlib.colors import Normalize
|
|
45
|
+
|
|
46
|
+
import EasyPlotLib as epl
|
|
47
|
+
|
|
48
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
49
|
+
RNG = np.random.default_rng(0)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# --- synthetic "maps": random smooth fields on lon/lat boxes whose Δlon != Δlat ----
|
|
53
|
+
def smooth_field(ny, nx, sigma=6):
|
|
54
|
+
"""A random field smoothed by FFT so it looks map-like, not white noise."""
|
|
55
|
+
a = RNG.standard_normal((ny, nx))
|
|
56
|
+
fy = np.exp(-0.5 * (np.fft.fftfreq(ny)[:, None] * ny / sigma) ** 2)
|
|
57
|
+
fx = np.exp(-0.5 * (np.fft.fftfreq(nx)[None, :] * nx / sigma) ** 2)
|
|
58
|
+
a = np.fft.ifft2(np.fft.fft2(a) * (fy * fx)).real
|
|
59
|
+
a -= a.min()
|
|
60
|
+
return a / a.max()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# (name, lon0, lon1, lat0, lat1) — a wide parent + two square-ish nests, like d01/d02.
|
|
64
|
+
BOXES = [
|
|
65
|
+
("parent (wide)", 103.0, 129.0, 27.0, 43.0), # Δlon 26, Δlat 16 -> aspect 1.62
|
|
66
|
+
("nest A", 111.5, 121.5, 31.5, 40.0), # Δlon 10, Δlat 8.5 -> aspect 1.18
|
|
67
|
+
("nest B", 111.5, 121.5, 31.5, 40.0),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def box_aspect(b):
|
|
72
|
+
"""Width/height of the lon/lat box (the value to feed width_ratios)."""
|
|
73
|
+
_, lo0, lo1, la0, la1 = b
|
|
74
|
+
return (lo1 - lo0) / (la1 - la0)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def draw_map(ax, b):
|
|
78
|
+
_, lo0, lo1, la0, la1 = b
|
|
79
|
+
data = smooth_field(120, int(120 * box_aspect(b)))
|
|
80
|
+
im = ax.imshow(data, origin="lower", extent=(lo0, lo1, la0, la1),
|
|
81
|
+
cmap=epl.COLORMAPS["sequential"], vmin=0, vmax=1, aspect="equal")
|
|
82
|
+
ax.set_xticks(np.linspace(lo0, lo1, 4).round())
|
|
83
|
+
ax.set_yticks(np.linspace(la0, la1, 4).round())
|
|
84
|
+
ax.tick_params(labelsize=6)
|
|
85
|
+
return im
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def heights(fig, ax_map, cb):
|
|
89
|
+
"""(map height, colourbar height) in figure fraction, after a draw."""
|
|
90
|
+
fig.canvas.draw()
|
|
91
|
+
return ax_map.get_position().height, cb.ax.get_position().height
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ----------------------------------------------------------------------------------
|
|
95
|
+
def build(fix_aspect, clamp_cbar, path, title):
|
|
96
|
+
"""Build the 3-map row. fix_aspect -> width_ratios; clamp_cbar -> fix B."""
|
|
97
|
+
epl.journal_style("nat2", palette="nature", inverted_aspect_ratio=0.40, apply=True)
|
|
98
|
+
wr = [box_aspect(b) for b in BOXES] if fix_aspect else [1, 1, 1]
|
|
99
|
+
fig, axs = plt.subplots(1, 3, layout="constrained",
|
|
100
|
+
gridspec_kw=dict(width_ratios=wr))
|
|
101
|
+
cbs = []
|
|
102
|
+
for n, (ax, b) in enumerate(zip(axs, BOXES)):
|
|
103
|
+
im = draw_map(ax, b)
|
|
104
|
+
ax.set_title(b[0], fontsize=7)
|
|
105
|
+
ax.annotate(**epl.subplot_labels(n, "a"))
|
|
106
|
+
extend = "max" if n == 2 else "neither" # mimic the real irrigation bar
|
|
107
|
+
cb = fig.colorbar(ScalarMappable(Normalize(0, 1), im.cmap), ax=ax,
|
|
108
|
+
fraction=0.05, pad=0.02, aspect=32, extend=extend)
|
|
109
|
+
cb.ax.tick_params(labelsize=6)
|
|
110
|
+
cbs.append(cb)
|
|
111
|
+
|
|
112
|
+
if clamp_cbar:
|
|
113
|
+
fig.canvas.draw()
|
|
114
|
+
fig.set_layout_engine("none")
|
|
115
|
+
for ax, cb in zip(axs, cbs):
|
|
116
|
+
pm, pc = ax.get_position(), cb.ax.get_position()
|
|
117
|
+
cb.ax.set_position([pc.x0, pm.y0, pc.width, pm.height])
|
|
118
|
+
|
|
119
|
+
fig.suptitle(title, fontsize=8)
|
|
120
|
+
fig.savefig(path, dpi=200)
|
|
121
|
+
ratios = [heights(fig, ax, cb)[1] / heights(fig, ax, cb)[0] for ax, cb in zip(axs, cbs)]
|
|
122
|
+
map_hs = [ax.get_position().height for ax in axs]
|
|
123
|
+
plt.close(fig)
|
|
124
|
+
return ratios, map_hs
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
before_path = os.path.join(HERE, "test_map_aspect_colorbar_before.png")
|
|
129
|
+
after_path = os.path.join(HERE, "test_map_aspect_colorbar_after.png")
|
|
130
|
+
|
|
131
|
+
# BEFORE: equal-width cells, colourbars sized to the cell (the bug).
|
|
132
|
+
r_before, h_before = build(fix_aspect=False, clamp_cbar=False,
|
|
133
|
+
path=before_path, title="BEFORE: equal cells, cbar=cell height")
|
|
134
|
+
# AFTER: width_ratios ~ aspect (equal map heights) + colourbar clamped to map.
|
|
135
|
+
r_after, h_after = build(fix_aspect=True, clamp_cbar=True,
|
|
136
|
+
path=after_path, title="AFTER: width_ratios~aspect + clamped cbar")
|
|
137
|
+
|
|
138
|
+
print(f"colourbar/map height ratio BEFORE = {[round(x, 2) for x in r_before]}")
|
|
139
|
+
print(f"colourbar/map height ratio AFTER = {[round(x, 2) for x in r_after]}")
|
|
140
|
+
print(f"map heights (fig frac) BEFORE = {[round(x, 3) for x in h_before]}")
|
|
141
|
+
print(f"map heights (fig frac) AFTER = {[round(x, 3) for x in h_after]}")
|
|
142
|
+
print("saved", before_path)
|
|
143
|
+
print("saved", after_path)
|
|
144
|
+
|
|
145
|
+
# Fix B: colourbars must match their map height (the whole point).
|
|
146
|
+
assert max(r_before) > 1.25, f"expected tall colourbars before fix, got {r_before}"
|
|
147
|
+
assert all(abs(r - 1.0) < 0.06 for r in r_after), \
|
|
148
|
+
f"colourbars not clamped to map height: {r_after}"
|
|
149
|
+
# Fix A: width_ratios ~ aspect makes the three maps nearly the same height
|
|
150
|
+
# (BEFORE spread ~0.075 -> AFTER ~0.02). NOTE the equality is only exact when the
|
|
151
|
+
# figure is tall enough that every map is WIDTH-limited; a very wide/short figure
|
|
152
|
+
# pushes the square-ish nests into being HEIGHT-limited, so a small residual remains.
|
|
153
|
+
assert (max(h_before) - min(h_before)) > 2 * (max(h_after) - min(h_after)), \
|
|
154
|
+
f"width_ratios~aspect should shrink the height spread: {h_before} -> {h_after}"
|
|
155
|
+
assert max(h_after) - min(h_after) < 0.03, \
|
|
156
|
+
f"map heights not equalised by width_ratios~aspect: {h_after}"
|
|
157
|
+
print("OK: colourbars clamped to map height; panel heights equalised.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|