|
| 1 | +import re |
| 2 | + |
| 3 | +from bidict import frozenbidict |
| 4 | +from matplotlib import axis |
| 5 | +import numpy as np |
| 6 | + |
| 7 | +from ..utils import round_to_multiple, splice |
| 8 | + |
| 9 | +_si_prefixes = frozenbidict( |
| 10 | + { |
| 11 | + "Y": 24, # yotta |
| 12 | + "Z": 21, # zetta |
| 13 | + "E": 18, # exa |
| 14 | + "P": 15, # peta |
| 15 | + "T": 12, # tera |
| 16 | + "G": 9, # giga |
| 17 | + "M": 6, # mega |
| 18 | + "k": 3, # kilo |
| 19 | + "": 0, # base |
| 20 | + "m": -3, # milli |
| 21 | + "u": -6, # micro |
| 22 | + "n": -9, # nano |
| 23 | + "p": -12, # pico |
| 24 | + "f": -15, # femto |
| 25 | + "a": -18, # atto |
| 26 | + "z": -21, # zepto |
| 27 | + "y": -24, # yocto |
| 28 | + }, |
| 29 | +) |
| 30 | + |
| 31 | + |
| 32 | +def compact_xaxis_units( |
| 33 | + ax: axis.Axis, |
| 34 | + base_unit: str = "s", |
| 35 | + regex_pattern_template: str = r"\((.*?){base_unit}\)", |
| 36 | +) -> None: |
| 37 | + r"""Adjusts the units on the x-axis of a matplotlib axis object to a more |
| 38 | + compact form. |
| 39 | +
|
| 40 | + This function finds the multiplier (such as 10^3, 10^6, etc.) used by |
| 41 | + matplotlib for the axis, identifies the current unit prefix (like k for |
| 42 | + kilo, M for mega), and then consolidates these into a new, more compact unit |
| 43 | + prefix. This is particularly useful for graphs where the axis values are |
| 44 | + very large or very small, and a more readable unit is desired. |
| 45 | +
|
| 46 | + Parameters |
| 47 | + ---------- |
| 48 | + ax : axis.Axis |
| 49 | + Axis object whose x-axis units are to be compacted inplace. |
| 50 | + base_unit : str, default "s" |
| 51 | + The base unit (without prefix) for the axis. |
| 52 | + regex_pattern_template : str, default r"\((.*?){base_unit}\)" |
| 53 | + A regular expression template used to identify and extract the unit |
| 54 | + prefix from the axis label. |
| 55 | +
|
| 56 | + The template must contain `{base_unit}` as a |
| 57 | + placeholder for the actual base unit. Default matches parenthesized expressions like "(ks)" for kiloseconds. |
| 58 | + """ |
| 59 | + regex_pattern = regex_pattern_template.format(base_unit=base_unit) |
| 60 | + regex = re.compile(regex_pattern) |
| 61 | + |
| 62 | + # force population of |
| 63 | + fig = ax.get_figure() |
| 64 | + fig.canvas.draw() |
| 65 | + |
| 66 | + # get unit multiplier chosen by matplotlib |
| 67 | + offset_text = ax.xaxis.get_offset_text() |
| 68 | + if offset_text is None: # handle unit (i.e., non) multiplier |
| 69 | + return |
| 70 | + offset_string = offset_text.get_text() |
| 71 | + if offset_string == "": # handle unit (i.e., non) multiplier |
| 72 | + return |
| 73 | + offset_amount = float(offset_string) |
| 74 | + |
| 75 | + assert str(offset_amount).count("1") == 1 # power of 10 |
| 76 | + |
| 77 | + # get unit prefix and multiplier in axis label |
| 78 | + old_label_string = ax.get_xlabel() |
| 79 | + (old_prefix_match,) = regex.finditer(old_label_string) |
| 80 | + old_prefix = old_prefix_match.group(1) |
| 81 | + old_multiplier_pow = _si_prefixes[old_prefix] |
| 82 | + old_multiplier = 10.0**old_multiplier_pow |
| 83 | + |
| 84 | + # calculate new prefix from net multiplier |
| 85 | + new_multiplier = offset_amount * old_multiplier |
| 86 | + approx_pow = np.log10(new_multiplier) |
| 87 | + new_multiplier_pow = round_to_multiple(approx_pow, multiple=3) |
| 88 | + assert np.isclose(approx_pow, new_multiplier_pow) |
| 89 | + new_prefix = _si_prefixes.inv[new_multiplier_pow] |
| 90 | + |
| 91 | + # apply new multiplier, consolidating offset axis scale and label unit |
| 92 | + def replace_group(match): |
| 93 | + # Extracting the captured group |
| 94 | + captured_group = match.group(1) |
| 95 | + # Defining the replacement value for the captured group |
| 96 | + return replacement_value |
| 97 | + |
| 98 | + ax.xaxis.get_offset_text().set(visible=False) # remove offset axis scale |
| 99 | + old_prefix_span = old_prefix_match.span(1) |
| 100 | + new_label_string = splice( |
| 101 | + old_label_string, |
| 102 | + old_prefix_span, |
| 103 | + new_prefix, |
| 104 | + ) |
| 105 | + ax.set_xlabel(new_label_string) |
0 commit comments