import os, re, platform import matplotlib as mpl import matplotlib.pyplot as plt import pandas as pd from datetime import datetime def plt_config(plt, custom_rcparams=None): """ Configure matplotlib runtime parameters for consistent styling in IEEETran-like figures. Parameters: - plt: The matplotlib module to configure. - custom_rcparams: (dict, optional) Custom runtime parameters to override or extend the defaults. Default is None. """ # In IEEETran, \textwidth is 7.1413 inches # - Use `\printinunitsof{in}\prntlen{\textwidth}` # - To output the \textwidth value in the generated document, # you need to use the `printlen` and `layouts` packages. textwidth_inches = 7.1413 # Configure figure dimensions: width as 0.5 * \textwidth and aspect ratio (16:9 or 4:3) aspect = 16 / 9 # Aspect ratio (can also use 4/3) axes_left, axes_right, axes_bottom, axes_top = 0.15, 0.9, 0.15, 0.9 axes_width = axes_right - axes_left axes_height = axes_top - axes_bottom fig_width = 0.5 * textwidth_inches fig_height = (fig_width * axes_width / aspect) / axes_height # Default runtime parameters # -- call plt.rcParams.keys() to list all available rcParameter default_rcparams = { "text.usetex": True, # Use LaTeX for rendering text "text.latex.preamble": r'\usepackage{amsmath,amsfonts}', # Load LaTeX packages "font.family": 'Times New Roman', # Set font family "font.weight": 'normal', # Set font weight "font.size": 10, # Set base font size "figure.figsize": (fig_width, fig_height), # Set figure size "lines.linewidth": 0.5, # Set line width "lines.markeredgewidth": 0.5, # Set marker edge width "lines.markersize": 2, # Set marker size "figure.subplot.bottom": axes_bottom, # Subplot bottom margin "figure.subplot.left": axes_left, # Subplot left margin "figure.subplot.right": axes_right, # Subplot right margin "figure.subplot.top": axes_top, # Subplot top margin "legend.markerscale": 1.0, # Scale of markers in the legend "legend.handleheight": 0.5, # Height of legend handles "legend.handlelength": 1.5, # Length of legend handles "legend.handletextpad": 0.25, # Padding between handle and text "legend.columnspacing": 1.5, # Spacing between legend columns "legend.labelspacing": 0.25, # Spacing between legend entries "legend.fontsize": 7, # Legend font size "legend.borderpad": 0.2, # Padding inside the legend box "axes.labelsize": 8, # Axis label font size "axes.linewidth": 0.5, # Axis line width "axes.grid": True, # Enable grid "axes.formatter.limits": (-3, 3), "axes.formatter.useoffset": True, "grid.color": '#0F0F0F', # Grid color "grid.alpha": 0.25, # Grid transparency "grid.linestyle": ':', # Grid line style "grid.linewidth": 0.5, # Grid line width "xtick.direction": "in", # Direction of x-ticks "ytick.direction": "in", # Direction of y-ticks "xtick.labelsize": 7, # Font size for x-tick labels "ytick.labelsize": 7 # Font size for y-tick labels } # Override or extend the default parameters if custom settings are provided if custom_rcparams is not None: default_rcparams.update(custom_rcparams) # Apply the final configuration to matplotlib # -- use plt.rcParams.keys() to list all available parameters plt.rcParams.update(default_rcparams) def plt_export(filename=None, format='pdf', folder='.', dpi=96, fig=None): """ Save the current Matplotlib figure or a specified figure in the selected image format. This function should be a standard part of chart scripts, enabling the "Export image" functionality of the IDE and `opp_charttool` to work seamlessly. Parameters: - filename (str, optional): The output file name. If it has no extension, one will be added based on the format. If missing or empty, a sanitized version of the chart name is used. - format (str): The desired file format for the export. Default is 'pdf'. Accepted formats are those supported by Matplotlib (e.g., 'png', 'jpg', 'svg', 'pdf', etc.). - folder (str): The directory where the image file will be saved. Default is the current directory. - dpi (int): Dots per inch setting, default is 96. For raster image formats, the image dimensions are produced as width (or height) times dpi. - fig (matplotlib.figure.Figure, optional): The Matplotlib Figure object to export. If None, the current active figure is used. Notes: - For export, even IDE-native charts are rendered using Matplotlib. - `dpi` controls the resolution for raster image formats while maintaining the chart's aspect ratio and relative sizes. """ # Validate the format valid_formats = mpl.figure.Figure().savefig.__code__.co_varnames if format.lower() not in mpl.figure.Figure().canvas.get_supported_filetypes().keys(): raise ValueError(f"Unsupported format '{format}'. Supported formats are: {', '.join(mpl.figure.Figure().canvas.get_supported_filetypes().keys())}") # Handle filename if not filename: # If filename is None or empty, use a default name filename = 'exported_plot' if not re.match(r".+\.\w{3,4}$", filename): filename += f".{format.lower()}" filepath = os.path.normpath(os.path.join(folder, filename)) os.makedirs(os.path.dirname(filepath) or ".", exist_ok=True) # Function to create metadata def _make_image_metadata(): creator_info = ( f"Matplotlib {mpl.__version__}; " f"Pandas {pd.__version__}; " f"Python {platform.python_version()}; " f"{platform.platform()}" ) return {"Creator": creator_info} # Select the figure to save target_fig = fig or plt.gcf() # Prepare metadata if supported metadata = _make_image_metadata() if format.lower() in ['pdf', 'svg', 'eps'] else None # Attempt to save the figure try: target_fig.savefig(filepath, format=format, dpi=dpi, metadata=metadata, bbox_inches='tight') print(f'Exported image to: "{filepath}" as {format}') except Exception as e: print(f"Failed to save with metadata due to: {e}. Retrying without metadata.") try: target_fig.savefig(filepath, format=format, dpi=dpi, bbox_inches='tight') print(f'Exported image to: "{filepath}" as {format} without metadata') except Exception as e: print(f"Failed to save the figure: {e}") def plt_export(filename=None, format='pdf', folder='.', dpi=96, fig=None): """ Save the current Matplotlib figure or a specified figure in the selected image format. This function should be a standard part of chart scripts, enabling the "Export image" functionality of the IDE and `opp_charttool` to work seamlessly. Parameters: - filename (str, optional): The output file name. If it has no extension, one will be added based on the format. If missing or empty, a default name 'exported_plot' is used. - format (str): The desired file format for the export. Default is 'pdf'. Accepted formats are those supported by Matplotlib (e.g., 'png', 'jpg', 'svg', 'pdf', etc.). - folder (str): The directory where the image file will be saved. Default is the current directory. - dpi (int): Dots per inch setting, default is 96. For raster image formats, the image dimensions are produced as width (or height) times dpi. - fig (matplotlib.figure.Figure, optional): The Matplotlib Figure object to export. If None, the current active figure is used. Notes: - For export, even IDE-native charts are rendered using Matplotlib. - `dpi` controls the resolution for raster image formats while maintaining the chart's aspect ratio and relative sizes. """ # Select the figure to save target_fig = fig or plt.gcf() # Validate the format supported_formats = target_fig.canvas.get_supported_filetypes() if format.lower() not in supported_formats: raise ValueError( f"Unsupported format '{format}'. Supported formats are: " f"{', '.join(supported_formats.keys())}" ) # Handle filename if not filename: # Generate a default filename with a timestamp timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f'exported_plot_{timestamp}' if not re.match(r".+\.\w{3,4}$", filename): filename += f".{format.lower()}" filepath = os.path.normpath(os.path.join(folder, filename)) os.makedirs(os.path.dirname(filepath) or ".", exist_ok=True) def _get_mpl_backend_for_image_format(format): return { "png": "AGG", "jpg": "AGG", "jpeg": "AGG", "tif": "AGG", "tiff": "AGG", "raw": "AGG", "svg": "SVG", "svgz": "SVG", "ps" : "PS", "eps": "PS", "pdf": "PDF", "pgf": "PGF", }.get(format.lower(), None) # Function to create metadata def _make_image_metadata(): creator_info = ( f"Matplotlib {mpl.__version__}; " f"Pandas {pd.__version__}; " f"Python {platform.python_version()}; " f"{platform.platform()}" ) return {"Creator": creator_info} # Prepare metadata if supported metadata = _make_image_metadata() # Explicitly select a non-interactive backend appropriate for the export format, # and keep auto-selection for any other formats not listed here. backend = _get_mpl_backend_for_image_format(format) # Attempt to save the figure try: target_fig.savefig( filepath, format=format, dpi=dpi, metadata=metadata, backend=backend ) print(f'Exported image to: "{filepath}" as {format}') except Exception as e: print( f"Failed to save with metadata due to: {e}. " "Retrying without metadata." ) try: target_fig.savefig( filepath, format=format, dpi=dpi, backend=backend ) print( f'Exported image to: "{filepath}" as {format} without metadata' ) except Exception as e_inner: print(f"Failed to save the figure: {e_inner}")