mgplot.finalise_plot
Functions to finalise and save plots to the file system.
1"""Functions to finalise and save plots to the file system.""" 2 3import re 4import unicodedata 5from collections.abc import Callable, Sequence 6from pathlib import Path 7from typing import Any, Final, NotRequired, Unpack 8 9import matplotlib.pyplot as plt 10from matplotlib.axes import Axes 11from matplotlib.figure import Figure, SubFigure 12 13from mgplot.keyword_checking import BaseKwargs, report_kwargs, validate_kwargs 14from mgplot.settings import get_setting 15 16# --- constants 17ME: Final[str] = "finalise_plot" 18MAX_FILENAME_LENGTH: Final[int] = 150 19DEFAULT_MARGIN: Final[float] = 0.02 20TIGHT_LAYOUT_PAD: Final[float] = 1.1 21FOOTNOTE_FONTSIZE: Final[int] = 8 22FOOTNOTE_FONTSTYLE: Final[str] = "italic" 23FOOTNOTE_COLOR: Final[str] = "#999999" 24ZERO_LINE_WIDTH: Final[float] = 0.66 25ZERO_LINE_COLOR: Final[str] = "#555555" 26ZERO_AXIS_ADJUSTMENT: Final[float] = 0.02 27DEFAULT_FILE_TITLE_NAME: Final[str] = "plot" 28 29 30class FinaliseKwargs(BaseKwargs): 31 """Keyword arguments for the finalise_plot function.""" 32 33 # --- value options 34 title: NotRequired[str | None] 35 xlabel: NotRequired[str | None] 36 ylabel: NotRequired[str | None] 37 xlim: NotRequired[tuple[float, float] | None] 38 ylim: NotRequired[tuple[float, float] | None] 39 xticks: NotRequired[list[float] | None] 40 yticks: NotRequired[list[float] | None] 41 xscale: NotRequired[str | None] 42 yscale: NotRequired[str | None] 43 # --- splat options 44 legend: NotRequired[bool | dict[str, Any] | None] 45 axhspan: NotRequired[dict[str, Any]] 46 axvspan: NotRequired[dict[str, Any]] 47 axhline: NotRequired[dict[str, Any]] 48 axvline: NotRequired[dict[str, Any]] 49 # --- options for annotations 50 lfooter: NotRequired[str] 51 rfooter: NotRequired[str] 52 lheader: NotRequired[str] 53 rheader: NotRequired[str] 54 # --- file/save options 55 pre_tag: NotRequired[str] 56 tag: NotRequired[str] 57 chart_dir: NotRequired[str] 58 file_type: NotRequired[str] 59 dpi: NotRequired[int] 60 figsize: NotRequired[tuple[float, float]] 61 show: NotRequired[bool] 62 # --- other options 63 preserve_lims: NotRequired[bool] 64 remove_legend: NotRequired[bool] 65 zero_y: NotRequired[bool] 66 y0: NotRequired[bool] 67 x0: NotRequired[bool] 68 dont_save: NotRequired[bool] 69 dont_close: NotRequired[bool] 70 71 72VALUE_KWARGS = ( 73 "title", 74 "xlabel", 75 "ylabel", 76 "xlim", 77 "ylim", 78 "xticks", 79 "yticks", 80 "xscale", 81 "yscale", 82) 83SPLAT_KWARGS = ( 84 "axhspan", 85 "axvspan", 86 "axhline", 87 "axvline", 88 "legend", # needs to be last in this tuple 89) 90HEADER_FOOTER_KWARGS = ( 91 "lfooter", 92 "rfooter", 93 "lheader", 94 "rheader", 95) 96 97 98def sanitize_filename(filename: str, max_length: int = MAX_FILENAME_LENGTH) -> str: 99 """Convert a string to a safe filename. 100 101 Args: 102 filename: The string to convert to a filename 103 max_length: Maximum length for the filename 104 105 Returns: 106 A safe filename string 107 108 """ 109 if not filename: 110 return "untitled" 111 112 # Normalize unicode characters (e.g., é -> e) 113 filename = unicodedata.normalize("NFKD", filename) 114 115 # Remove non-ASCII characters 116 filename = filename.encode("ascii", "ignore").decode("ascii") 117 118 # Convert to lowercase 119 filename = filename.lower() 120 121 # Replace spaces and other separators with hyphens 122 filename = re.sub(r"[\s\-_]+", "-", filename) 123 124 # Remove unsafe characters, keeping only alphanumeric and hyphens 125 filename = re.sub(r"[^a-z0-9\-]", "", filename) 126 127 # Remove leading/trailing hyphens and collapse multiple hyphens 128 filename = re.sub(r"^-+|-+$", "", filename) 129 filename = re.sub(r"-+", "-", filename) 130 131 # Truncate to max length 132 if len(filename) > max_length: 133 filename = filename[:max_length].rstrip("-") 134 135 # Ensure we have a valid filename 136 return filename if filename else "untitled" 137 138 139def make_legend(axes: Axes, *, legend: None | bool | dict[str, Any]) -> None: 140 """Create a legend for the plot.""" 141 if legend is None or legend is False: 142 return 143 144 if legend is True: # use the global default settings 145 legend = get_setting("legend") 146 147 if isinstance(legend, dict): 148 axes.legend(**legend) 149 return 150 151 print(f"Warning: expected dict argument for legend, but got {type(legend)}.") 152 153 154def apply_value_kwargs(axes: Axes, value_kwargs_: Sequence[str], **kwargs: Unpack[FinaliseKwargs]) -> None: 155 """Set matplotlib elements by name using Axes.set(). 156 157 Tricky: some plotting functions may set the xlabel or ylabel. 158 So ... we will set these if a setting is explicitly provided. If no 159 setting is provided, we will set to None if they are not already set. 160 If they have already been set, we will not change them. 161 162 """ 163 # --- preliminary 164 function: dict[str, Callable[[], str]] = { 165 "xlabel": axes.get_xlabel, 166 "ylabel": axes.get_ylabel, 167 "title": axes.get_title, 168 } 169 170 def fail() -> str: 171 return "" 172 173 # --- loop over potential value settings 174 for setting in value_kwargs_: 175 value = kwargs.get(setting) 176 if setting in kwargs: 177 # deliberately set, so we will action 178 axes.set(**{setting: value}) 179 continue 180 required_to_set = ("title", "xlabel", "ylabel") 181 if setting not in required_to_set: 182 # not set - and not required - so we can skip 183 continue 184 185 # we will set these 'required_to_set' ones 186 # provided they are not already set 187 already_set = function.get(setting, fail)() 188 if already_set and value is None: 189 continue 190 191 # if we get here, we will set the value (implicitly to None) 192 axes.set(**{setting: value}) 193 194 195def apply_splat_kwargs(axes: Axes, settings: tuple, **kwargs: Unpack[FinaliseKwargs]) -> None: 196 """Set matplotlib elements dynamically using setting_name and splat.""" 197 for method_name in settings: 198 if method_name in kwargs: 199 if method_name == "legend": 200 # special case for legend 201 legend_value = kwargs.get(method_name) 202 if isinstance(legend_value, (bool, dict, type(None))): 203 make_legend(axes, legend=legend_value) 204 else: 205 print(f"Warning: expected bool, dict, or None for legend, but got {type(legend_value)}.") 206 continue 207 208 if method_name not in kwargs: 209 continue 210 value = kwargs.get(method_name) 211 if value is None or value is False: 212 continue 213 214 if value is True: # use the global default settings 215 value = get_setting(method_name) 216 217 # splat the kwargs to the method 218 if isinstance(value, dict): 219 method = getattr(axes, method_name) 220 method(**value) 221 else: 222 print( 223 f"Warning expected dict argument for {method_name} but got {type(value)}.", 224 ) 225 226 227def apply_annotations(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 228 """Set figure size and apply chart annotations.""" 229 fig = axes.figure 230 fig_size = kwargs.get("figsize", get_setting("figsize")) 231 if not isinstance(fig, SubFigure): 232 fig.set_size_inches(*fig_size) 233 234 annotations = { 235 "rfooter": (0.99, 0.001, "right", "bottom"), 236 "lfooter": (0.01, 0.001, "left", "bottom"), 237 "rheader": (0.99, 0.999, "right", "top"), 238 "lheader": (0.01, 0.999, "left", "top"), 239 } 240 241 for annotation in HEADER_FOOTER_KWARGS: 242 if annotation in kwargs: 243 x_pos, y_pos, h_align, v_align = annotations[annotation] 244 fig.text( 245 x_pos, 246 y_pos, 247 str(kwargs.get(annotation, "")), 248 ha=h_align, 249 va=v_align, 250 fontsize=FOOTNOTE_FONTSIZE, 251 fontstyle=FOOTNOTE_FONTSTYLE, 252 color=FOOTNOTE_COLOR, 253 ) 254 255 256def apply_late_kwargs(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 257 """Apply settings found in kwargs, after plotting the data.""" 258 apply_splat_kwargs(axes, SPLAT_KWARGS, **kwargs) 259 260 261def apply_kwargs(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 262 """Apply settings found in kwargs.""" 263 264 def check_kwargs(name: str) -> bool: 265 return name in kwargs and bool(kwargs.get(name)) 266 267 apply_value_kwargs(axes, VALUE_KWARGS, **kwargs) 268 apply_annotations(axes, **kwargs) 269 270 if check_kwargs("zero_y"): 271 bottom, top = axes.get_ylim() 272 adj = (top - bottom) * ZERO_AXIS_ADJUSTMENT 273 if bottom > -adj: 274 axes.set_ylim(bottom=-adj) 275 if top < adj: 276 axes.set_ylim(top=adj) 277 278 if check_kwargs("y0"): 279 low, high = axes.get_ylim() 280 if low < 0 < high: 281 axes.axhline(y=0, lw=ZERO_LINE_WIDTH, c=ZERO_LINE_COLOR) 282 283 if check_kwargs("x0"): 284 low, high = axes.get_xlim() 285 if low < 0 < high: 286 axes.axvline(x=0, lw=ZERO_LINE_WIDTH, c=ZERO_LINE_COLOR) 287 288 289def save_to_file(fig: Figure, **kwargs: Unpack[FinaliseKwargs]) -> None: 290 """Save the figure to file.""" 291 saving = not kwargs.get("dont_save", False) # save by default 292 if not saving: 293 return 294 295 try: 296 chart_dir = Path(kwargs.get("chart_dir", get_setting("chart_dir"))) 297 298 # Ensure directory exists 299 chart_dir.mkdir(parents=True, exist_ok=True) 300 301 title = kwargs.get("title", "") 302 pre_tag = kwargs.get("pre_tag", "") 303 tag = kwargs.get("tag", "") 304 file_title = sanitize_filename(title if title else DEFAULT_FILE_TITLE_NAME) 305 file_type = kwargs.get("file_type", get_setting("file_type")).lower() 306 dpi = kwargs.get("dpi", get_setting("dpi")) 307 308 # Construct filename components safely 309 filename_parts = [] 310 if pre_tag: 311 filename_parts.append(sanitize_filename(pre_tag)) 312 filename_parts.append(file_title) 313 if tag: 314 filename_parts.append(sanitize_filename(tag)) 315 316 # Join filename parts and add extension 317 filename = "-".join(filter(None, filename_parts)) 318 filepath = chart_dir / f"{filename}.{file_type}" 319 320 fig.savefig(filepath, dpi=dpi) 321 322 except ( 323 OSError, 324 PermissionError, 325 FileNotFoundError, 326 ValueError, 327 RuntimeError, 328 TypeError, 329 UnicodeError, 330 ) as e: 331 print(f"Error: Could not save plot to file: {e}") 332 333 334# - public functions for finalise_plot() 335 336 337def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 338 """Finalise and save plots to the file system. 339 340 The filename for the saved plot is constructed from the global 341 chart_dir, the plot's title, any specified tag text, and the 342 file_type for the plot. 343 344 Args: 345 axes: Axes - matplotlib axes object - required 346 kwargs: FinaliseKwargs 347 348 """ 349 # --- check the kwargs 350 report_kwargs(caller=ME, **kwargs) 351 validate_kwargs(schema=FinaliseKwargs, caller=ME, **kwargs) 352 353 # --- sanity checks 354 if len(axes.get_children()) < 1: 355 print(f"Warning: {ME}() called with an empty axes, which was ignored.") 356 return 357 358 # --- remember axis-limits should we need to restore thems 359 xlim, ylim = axes.get_xlim(), axes.get_ylim() 360 361 # margins 362 axes.margins(DEFAULT_MARGIN) 363 axes.autoscale(tight=False) # This is problematic ... 364 365 apply_kwargs(axes, **kwargs) 366 367 # tight layout and save the figure 368 fig = axes.figure 369 if kwargs.get("preserve_lims"): 370 # restore the original limits of the axes 371 axes.set_xlim(xlim) 372 axes.set_ylim(ylim) 373 if not isinstance(fig, SubFigure): 374 fig.tight_layout(pad=TIGHT_LAYOUT_PAD) 375 apply_late_kwargs(axes, **kwargs) 376 legend = axes.get_legend() 377 if legend and kwargs.get("remove_legend", False): 378 legend.remove() 379 if not isinstance(fig, SubFigure): 380 save_to_file(fig, **kwargs) 381 382 # show the plot in Jupyter Lab 383 if kwargs.get("show"): 384 plt.show() 385 386 # And close 387 if not kwargs.get("dont_close", False): 388 plt.close()
31class FinaliseKwargs(BaseKwargs): 32 """Keyword arguments for the finalise_plot function.""" 33 34 # --- value options 35 title: NotRequired[str | None] 36 xlabel: NotRequired[str | None] 37 ylabel: NotRequired[str | None] 38 xlim: NotRequired[tuple[float, float] | None] 39 ylim: NotRequired[tuple[float, float] | None] 40 xticks: NotRequired[list[float] | None] 41 yticks: NotRequired[list[float] | None] 42 xscale: NotRequired[str | None] 43 yscale: NotRequired[str | None] 44 # --- splat options 45 legend: NotRequired[bool | dict[str, Any] | None] 46 axhspan: NotRequired[dict[str, Any]] 47 axvspan: NotRequired[dict[str, Any]] 48 axhline: NotRequired[dict[str, Any]] 49 axvline: NotRequired[dict[str, Any]] 50 # --- options for annotations 51 lfooter: NotRequired[str] 52 rfooter: NotRequired[str] 53 lheader: NotRequired[str] 54 rheader: NotRequired[str] 55 # --- file/save options 56 pre_tag: NotRequired[str] 57 tag: NotRequired[str] 58 chart_dir: NotRequired[str] 59 file_type: NotRequired[str] 60 dpi: NotRequired[int] 61 figsize: NotRequired[tuple[float, float]] 62 show: NotRequired[bool] 63 # --- other options 64 preserve_lims: NotRequired[bool] 65 remove_legend: NotRequired[bool] 66 zero_y: NotRequired[bool] 67 y0: NotRequired[bool] 68 x0: NotRequired[bool] 69 dont_save: NotRequired[bool] 70 dont_close: NotRequired[bool]
Keyword arguments for the finalise_plot function.
99def sanitize_filename(filename: str, max_length: int = MAX_FILENAME_LENGTH) -> str: 100 """Convert a string to a safe filename. 101 102 Args: 103 filename: The string to convert to a filename 104 max_length: Maximum length for the filename 105 106 Returns: 107 A safe filename string 108 109 """ 110 if not filename: 111 return "untitled" 112 113 # Normalize unicode characters (e.g., é -> e) 114 filename = unicodedata.normalize("NFKD", filename) 115 116 # Remove non-ASCII characters 117 filename = filename.encode("ascii", "ignore").decode("ascii") 118 119 # Convert to lowercase 120 filename = filename.lower() 121 122 # Replace spaces and other separators with hyphens 123 filename = re.sub(r"[\s\-_]+", "-", filename) 124 125 # Remove unsafe characters, keeping only alphanumeric and hyphens 126 filename = re.sub(r"[^a-z0-9\-]", "", filename) 127 128 # Remove leading/trailing hyphens and collapse multiple hyphens 129 filename = re.sub(r"^-+|-+$", "", filename) 130 filename = re.sub(r"-+", "-", filename) 131 132 # Truncate to max length 133 if len(filename) > max_length: 134 filename = filename[:max_length].rstrip("-") 135 136 # Ensure we have a valid filename 137 return filename if filename else "untitled"
Convert a string to a safe filename.
Args: filename: The string to convert to a filename max_length: Maximum length for the filename
Returns: A safe filename string
140def make_legend(axes: Axes, *, legend: None | bool | dict[str, Any]) -> None: 141 """Create a legend for the plot.""" 142 if legend is None or legend is False: 143 return 144 145 if legend is True: # use the global default settings 146 legend = get_setting("legend") 147 148 if isinstance(legend, dict): 149 axes.legend(**legend) 150 return 151 152 print(f"Warning: expected dict argument for legend, but got {type(legend)}.")
Create a legend for the plot.
155def apply_value_kwargs(axes: Axes, value_kwargs_: Sequence[str], **kwargs: Unpack[FinaliseKwargs]) -> None: 156 """Set matplotlib elements by name using Axes.set(). 157 158 Tricky: some plotting functions may set the xlabel or ylabel. 159 So ... we will set these if a setting is explicitly provided. If no 160 setting is provided, we will set to None if they are not already set. 161 If they have already been set, we will not change them. 162 163 """ 164 # --- preliminary 165 function: dict[str, Callable[[], str]] = { 166 "xlabel": axes.get_xlabel, 167 "ylabel": axes.get_ylabel, 168 "title": axes.get_title, 169 } 170 171 def fail() -> str: 172 return "" 173 174 # --- loop over potential value settings 175 for setting in value_kwargs_: 176 value = kwargs.get(setting) 177 if setting in kwargs: 178 # deliberately set, so we will action 179 axes.set(**{setting: value}) 180 continue 181 required_to_set = ("title", "xlabel", "ylabel") 182 if setting not in required_to_set: 183 # not set - and not required - so we can skip 184 continue 185 186 # we will set these 'required_to_set' ones 187 # provided they are not already set 188 already_set = function.get(setting, fail)() 189 if already_set and value is None: 190 continue 191 192 # if we get here, we will set the value (implicitly to None) 193 axes.set(**{setting: value})
Set matplotlib elements by name using Axes.set().
Tricky: some plotting functions may set the xlabel or ylabel. So ... we will set these if a setting is explicitly provided. If no setting is provided, we will set to None if they are not already set. If they have already been set, we will not change them.
196def apply_splat_kwargs(axes: Axes, settings: tuple, **kwargs: Unpack[FinaliseKwargs]) -> None: 197 """Set matplotlib elements dynamically using setting_name and splat.""" 198 for method_name in settings: 199 if method_name in kwargs: 200 if method_name == "legend": 201 # special case for legend 202 legend_value = kwargs.get(method_name) 203 if isinstance(legend_value, (bool, dict, type(None))): 204 make_legend(axes, legend=legend_value) 205 else: 206 print(f"Warning: expected bool, dict, or None for legend, but got {type(legend_value)}.") 207 continue 208 209 if method_name not in kwargs: 210 continue 211 value = kwargs.get(method_name) 212 if value is None or value is False: 213 continue 214 215 if value is True: # use the global default settings 216 value = get_setting(method_name) 217 218 # splat the kwargs to the method 219 if isinstance(value, dict): 220 method = getattr(axes, method_name) 221 method(**value) 222 else: 223 print( 224 f"Warning expected dict argument for {method_name} but got {type(value)}.", 225 )
Set matplotlib elements dynamically using setting_name and splat.
228def apply_annotations(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 229 """Set figure size and apply chart annotations.""" 230 fig = axes.figure 231 fig_size = kwargs.get("figsize", get_setting("figsize")) 232 if not isinstance(fig, SubFigure): 233 fig.set_size_inches(*fig_size) 234 235 annotations = { 236 "rfooter": (0.99, 0.001, "right", "bottom"), 237 "lfooter": (0.01, 0.001, "left", "bottom"), 238 "rheader": (0.99, 0.999, "right", "top"), 239 "lheader": (0.01, 0.999, "left", "top"), 240 } 241 242 for annotation in HEADER_FOOTER_KWARGS: 243 if annotation in kwargs: 244 x_pos, y_pos, h_align, v_align = annotations[annotation] 245 fig.text( 246 x_pos, 247 y_pos, 248 str(kwargs.get(annotation, "")), 249 ha=h_align, 250 va=v_align, 251 fontsize=FOOTNOTE_FONTSIZE, 252 fontstyle=FOOTNOTE_FONTSTYLE, 253 color=FOOTNOTE_COLOR, 254 )
Set figure size and apply chart annotations.
257def apply_late_kwargs(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 258 """Apply settings found in kwargs, after plotting the data.""" 259 apply_splat_kwargs(axes, SPLAT_KWARGS, **kwargs)
Apply settings found in kwargs, after plotting the data.
262def apply_kwargs(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 263 """Apply settings found in kwargs.""" 264 265 def check_kwargs(name: str) -> bool: 266 return name in kwargs and bool(kwargs.get(name)) 267 268 apply_value_kwargs(axes, VALUE_KWARGS, **kwargs) 269 apply_annotations(axes, **kwargs) 270 271 if check_kwargs("zero_y"): 272 bottom, top = axes.get_ylim() 273 adj = (top - bottom) * ZERO_AXIS_ADJUSTMENT 274 if bottom > -adj: 275 axes.set_ylim(bottom=-adj) 276 if top < adj: 277 axes.set_ylim(top=adj) 278 279 if check_kwargs("y0"): 280 low, high = axes.get_ylim() 281 if low < 0 < high: 282 axes.axhline(y=0, lw=ZERO_LINE_WIDTH, c=ZERO_LINE_COLOR) 283 284 if check_kwargs("x0"): 285 low, high = axes.get_xlim() 286 if low < 0 < high: 287 axes.axvline(x=0, lw=ZERO_LINE_WIDTH, c=ZERO_LINE_COLOR)
Apply settings found in kwargs.
290def save_to_file(fig: Figure, **kwargs: Unpack[FinaliseKwargs]) -> None: 291 """Save the figure to file.""" 292 saving = not kwargs.get("dont_save", False) # save by default 293 if not saving: 294 return 295 296 try: 297 chart_dir = Path(kwargs.get("chart_dir", get_setting("chart_dir"))) 298 299 # Ensure directory exists 300 chart_dir.mkdir(parents=True, exist_ok=True) 301 302 title = kwargs.get("title", "") 303 pre_tag = kwargs.get("pre_tag", "") 304 tag = kwargs.get("tag", "") 305 file_title = sanitize_filename(title if title else DEFAULT_FILE_TITLE_NAME) 306 file_type = kwargs.get("file_type", get_setting("file_type")).lower() 307 dpi = kwargs.get("dpi", get_setting("dpi")) 308 309 # Construct filename components safely 310 filename_parts = [] 311 if pre_tag: 312 filename_parts.append(sanitize_filename(pre_tag)) 313 filename_parts.append(file_title) 314 if tag: 315 filename_parts.append(sanitize_filename(tag)) 316 317 # Join filename parts and add extension 318 filename = "-".join(filter(None, filename_parts)) 319 filepath = chart_dir / f"{filename}.{file_type}" 320 321 fig.savefig(filepath, dpi=dpi) 322 323 except ( 324 OSError, 325 PermissionError, 326 FileNotFoundError, 327 ValueError, 328 RuntimeError, 329 TypeError, 330 UnicodeError, 331 ) as e: 332 print(f"Error: Could not save plot to file: {e}")
Save the figure to file.
338def finalise_plot(axes: Axes, **kwargs: Unpack[FinaliseKwargs]) -> None: 339 """Finalise and save plots to the file system. 340 341 The filename for the saved plot is constructed from the global 342 chart_dir, the plot's title, any specified tag text, and the 343 file_type for the plot. 344 345 Args: 346 axes: Axes - matplotlib axes object - required 347 kwargs: FinaliseKwargs 348 349 """ 350 # --- check the kwargs 351 report_kwargs(caller=ME, **kwargs) 352 validate_kwargs(schema=FinaliseKwargs, caller=ME, **kwargs) 353 354 # --- sanity checks 355 if len(axes.get_children()) < 1: 356 print(f"Warning: {ME}() called with an empty axes, which was ignored.") 357 return 358 359 # --- remember axis-limits should we need to restore thems 360 xlim, ylim = axes.get_xlim(), axes.get_ylim() 361 362 # margins 363 axes.margins(DEFAULT_MARGIN) 364 axes.autoscale(tight=False) # This is problematic ... 365 366 apply_kwargs(axes, **kwargs) 367 368 # tight layout and save the figure 369 fig = axes.figure 370 if kwargs.get("preserve_lims"): 371 # restore the original limits of the axes 372 axes.set_xlim(xlim) 373 axes.set_ylim(ylim) 374 if not isinstance(fig, SubFigure): 375 fig.tight_layout(pad=TIGHT_LAYOUT_PAD) 376 apply_late_kwargs(axes, **kwargs) 377 legend = axes.get_legend() 378 if legend and kwargs.get("remove_legend", False): 379 legend.remove() 380 if not isinstance(fig, SubFigure): 381 save_to_file(fig, **kwargs) 382 383 # show the plot in Jupyter Lab 384 if kwargs.get("show"): 385 plt.show() 386 387 # And close 388 if not kwargs.get("dont_close", False): 389 plt.close()
Finalise and save plots to the file system.
The filename for the saved plot is constructed from the global chart_dir, the plot's title, any specified tag text, and the file_type for the plot.
Args: axes: Axes - matplotlib axes object - required kwargs: FinaliseKwargs