|
8 | 8 | from tidy3d.exceptions import Tidy3dKeyError
|
9 | 9 |
|
10 | 10 |
|
11 |
| -def _create_unit_aware_locator(): |
12 |
| - """Create UnitAwareLocator lazily due to matplotlib import restrictions.""" |
13 |
| - import matplotlib.ticker as ticker |
14 |
| - |
15 |
| - class UnitAwareLocator(ticker.Locator): |
16 |
| - """Custom tick locator that places ticks at nice positions in the target unit.""" |
17 |
| - |
18 |
| - def __init__(self, scale_factor: float): |
19 |
| - """ |
20 |
| - Parameters |
21 |
| - ---------- |
22 |
| - scale_factor : float |
23 |
| - Factor to convert from micrometers to the target unit. |
24 |
| - """ |
25 |
| - super().__init__() |
26 |
| - self.scale_factor = scale_factor |
27 |
| - |
28 |
| - def __call__(self): |
29 |
| - vmin, vmax = self.axis.get_view_interval() |
30 |
| - return self.tick_values(vmin, vmax) |
31 |
| - |
32 |
| - def view_limits(self, vmin, vmax): |
33 |
| - """Override to prevent matplotlib from adjusting our limits.""" |
34 |
| - return vmin, vmax |
35 |
| - |
36 |
| - def tick_values(self, vmin, vmax): |
37 |
| - # convert the view range to the target unit |
38 |
| - vmin_unit = vmin * self.scale_factor |
39 |
| - vmax_unit = vmax * self.scale_factor |
40 |
| - |
41 |
| - # tolerance for floating point comparisons in target unit |
42 |
| - unit_range = vmax_unit - vmin_unit |
43 |
| - unit_tol = unit_range * 1e-8 |
44 |
| - |
45 |
| - locator = ticker.MaxNLocator(nbins=11, prune=None, min_n_ticks=2) |
46 |
| - |
47 |
| - ticks_unit = locator.tick_values(vmin_unit, vmax_unit) |
48 |
| - |
49 |
| - # ensure we have ticks that cover the full range |
50 |
| - if len(ticks_unit) > 0: |
51 |
| - if ticks_unit[0] > vmin_unit + unit_tol or ticks_unit[-1] < vmax_unit - unit_tol: |
52 |
| - # try with fewer bins to get better coverage |
53 |
| - for n in [10, 9, 8, 7, 6, 5]: |
54 |
| - locator = ticker.MaxNLocator(nbins=n, prune=None, min_n_ticks=2) |
55 |
| - ticks_unit = locator.tick_values(vmin_unit, vmax_unit) |
56 |
| - if ( |
57 |
| - len(ticks_unit) >= 3 |
58 |
| - and ticks_unit[0] <= vmin_unit + unit_tol |
59 |
| - and ticks_unit[-1] >= vmax_unit - unit_tol |
60 |
| - ): |
61 |
| - break |
62 |
| - |
63 |
| - # if still no good coverage, manually ensure edge coverage |
64 |
| - if len(ticks_unit) > 0: |
65 |
| - if ( |
66 |
| - ticks_unit[0] > vmin_unit + unit_tol |
67 |
| - or ticks_unit[-1] < vmax_unit - unit_tol |
68 |
| - ): |
69 |
| - # find a reasonable step size from existing ticks |
70 |
| - if len(ticks_unit) > 1: |
71 |
| - step = ticks_unit[1] - ticks_unit[0] |
72 |
| - else: |
73 |
| - step = unit_range / 5 |
74 |
| - |
75 |
| - # extend the range to ensure coverage |
76 |
| - extended_min = vmin_unit - step |
77 |
| - extended_max = vmax_unit + step |
78 |
| - |
79 |
| - # try one more time with extended range |
80 |
| - locator = ticker.MaxNLocator(nbins=8, prune=None, min_n_ticks=2) |
81 |
| - ticks_unit = locator.tick_values(extended_min, extended_max) |
82 |
| - |
83 |
| - # filter to reasonable bounds around the original range |
84 |
| - ticks_unit = [ |
85 |
| - t |
86 |
| - for t in ticks_unit |
87 |
| - if t >= vmin_unit - step / 2 and t <= vmax_unit + step / 2 |
88 |
| - ] |
89 |
| - |
90 |
| - # convert the nice ticks back to the original data unit (micrometers) |
91 |
| - ticks_um = ticks_unit / self.scale_factor |
92 |
| - |
93 |
| - # filter to ensure ticks are within bounds (with small tolerance) |
94 |
| - eps = (vmax - vmin) * 1e-8 |
95 |
| - return [tick for tick in ticks_um if vmin - eps <= tick <= vmax + eps] |
96 |
| - |
97 |
| - return UnitAwareLocator |
98 |
| - |
99 |
| - |
100 | 11 | def make_ax() -> Ax:
|
101 | 12 | """makes an empty ``ax``."""
|
102 | 13 | import matplotlib.pyplot as plt
|
@@ -161,24 +72,14 @@ def set_default_labels_and_title(
|
161 | 72 | )
|
162 | 73 | ax.set_xlabel(f"{xlabel} ({plot_length_units})")
|
163 | 74 | ax.set_ylabel(f"{ylabel} ({plot_length_units})")
|
164 |
| - |
| 75 | + # Formatter to help plot in arbitrary units |
165 | 76 | scale_factor = UnitScaling[plot_length_units]
|
166 |
| - |
167 |
| - # for imperial units, use custom tick locator for nice tick positions |
168 |
| - if plot_length_units in ["mil", "in"]: |
169 |
| - UnitAwareLocator = _create_unit_aware_locator() |
170 |
| - x_locator = UnitAwareLocator(scale_factor) |
171 |
| - y_locator = UnitAwareLocator(scale_factor) |
172 |
| - ax.xaxis.set_major_locator(x_locator) |
173 |
| - ax.yaxis.set_major_locator(y_locator) |
174 |
| - |
175 | 77 | formatter = ticker.FuncFormatter(lambda y, _: f"{y * scale_factor:.2f}")
|
176 |
| - |
177 | 78 | ax.xaxis.set_major_formatter(formatter)
|
178 | 79 | ax.yaxis.set_major_formatter(formatter)
|
179 |
| - |
180 |
| - position_scaled = position * scale_factor |
181 |
| - ax.set_title(f"cross section at {'xyz'[axis]}={position_scaled:.2f} ({plot_length_units})") |
| 80 | + ax.set_title( |
| 81 | + f"cross section at {'xyz'[axis]}={position * scale_factor:.2f} ({plot_length_units})" |
| 82 | + ) |
182 | 83 | else:
|
183 | 84 | ax.set_xlabel(xlabel)
|
184 | 85 | ax.set_ylabel(ylabel)
|
|
0 commit comments