[docs]defcreate_example_plots(ax:Axes|Axes3D,base_name:str,path:Path=Path("."),)->None:"""Create plots from an Axes object and save them as a svg file. If the ``SMOKE_TEST`` variable is set, no plots are being created and this method immediately returns. The function attempts to read the predefined themes from ``plotting_themes.json``. For each theme it finds, a file ``{base_name}_{theme}.svg`` is being created. If the file cannot be found, if the JSON cannot be loaded or if the JSON is not well configured, a fallback theme is used. Args: ax: The Axes object containing the figure that should be plotted. base_name: The base name that is used for naming the output files. path: Optional path to the directory in which the plots should be saved. Returns: The ``Figure`` containing ``ax`` """# Check whether we immediately return due to just running a SMOKE_TESTif"SMOKE_TEST"inos.environ:return# Define a fallback theme in case no configuration is foundfallback:dict[str,Any]={"color":"black","figsize":(24,8),"fontsize":22,"framealpha":0.3,}# Try to find the plotting themes by backtracking# Get the absolute path of the current scriptscript_path=Path(sys.argv[0]).resolve()while(notPath(script_path/"plotting_themes.json").is_file()andscript_path!=script_path.parent):script_path=script_path.parentifscript_path==script_path.parent:warnings.warn("No themes for plotting found. A fallback theme is used.")themes={"fallback":fallback}else:# Open the file containing all the themes# If we reach this point, we know that the file exists, so we try to load it.# If the file is no proper json, the fallback theme is used.try:themes=json.load(open(script_path/"plotting_themes.json"))exceptjson.JSONDecodeError:warnings.warn("The JSON containing the themes could not be loaded.""A fallback theme is used.",UserWarning,)themes={"fallback":fallback}fortheme_nameinthemes:# Get all of the values from the themes# TODO This can probably be generalized and improved later on such that the# keys fit the rc_params of matplotlib# TODO We might want to add a generalization herenecessary_keys=("color","figsize","fontsize","framealpha")ifnotall(keyinthemes[theme_name]forkeyinnecessary_keys):warnings.warn("Provided theme does not contain the necessary keys.""Using a fallback theme instead.",UserWarning,)current_theme=fallbackelse:current_theme=themes[theme_name]color:str=current_theme["color"]figsize:tuple[int,int]=current_theme["figsize"]fontsize:int=current_theme["fontsize"]framealpha:float=current_theme["framealpha"]# Adjust the axes of the plotforkeyinax.spines.keys():ax.spines[key].set_color(color)ax.xaxis.label.set_color(color)ax.xaxis.label.set_fontsize(fontsize)ax.yaxis.label.set_color(color)ax.yaxis.label.set_fontsize(fontsize)ifisinstance(ax,Axes3D):ax.zaxis.label.set_color(color)ax.zaxis.label.set_fontsize(fontsize)# Adjust the size of the ax# mypy thinks that ax.figure might become None, hence the explicit ignoreifisinstance(ax.figure,Figure):ax.figure.set_size_inches(*figsize)else:warnings.warn("Could not adjust size of plot due to it not being a Figure.")# Adjust the labelsticklabels=ax.get_xticklabels()+ax.get_yticklabels()ifisinstance(ax,Axes3D):ticklabels+=ax.get_zticklabels()forlabelinticklabels:label.set_color(color)label.set_fontsize(fontsize)# Adjust the legend if it existslegend=ax.get_legend()iflegend:legend.get_frame().set_alpha(framealpha)legend.get_title().set_color(color)legend.get_title().set_fontsize(fontsize)fortextinlegend.get_texts():text.set_fontsize(fontsize)text.set_color(color)output_path=Path(path,f"{base_name}_{theme_name}.svg")# mypy thinks that ax.figure might become None, hence the explicit ignoreifisinstance(ax.figure,Figure):ax.figure.savefig(output_path,format="svg",transparent=True,)else:warnings.warn("Plots could not be saved.")plt.close()
[docs]defindent(text:str,amount:int=3,ch:str=" ")->str:"""Indent a given text by a certain amount."""padding=amount*chreturn"".join(padding+lineforlineintext.splitlines(keepends=True))
[docs]defto_string(header:str,*fields:Any,single_line:bool=False)->str:"""Create a nested string representation. Args: header: The header, typically the name of a class. *fields: Fields to be printed with an indentation. single_line: If ``True``, print the representation on a single line. Only applicable when given a single field. Raises: ValueError: If ``single_line`` is ``True`` but ``fields`` contains more than one element. Returns: The string representation with indented fields. """ifsingle_line:iflen(fields)>1:raiseValueError("``single_line`` is only applicable when given a single field.")# Since single line headers look ugly without a ":", we add it manuallyheader=headerifheader.endswith(":")elseheader+":"returnf"{header}{str(fields[0])}"return"\n".join([header]+[indent(str(f))forfinfields])