Source code for sattoolbox.plots.raw_material.fm_plots

# -*- coding: utf-8 -*-
"""
Plot functions from Felix Micus.

@author: FM
"""


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters
import matplotlib.font_manager as font_manager
import matplotlib.dates as mdates
from itertools import cycle
#import locale
#from plotly.subplots import make_subplots
#import fm_utilities as util
#from .internals import plot_utils as plot_utils

import sattoolbox.plots.raw_material.internals.plot_utils as plot_utils   

#import internals.fluid_prop_utils as fp_util
register_matplotlib_converters()
register_matplotlib_converters()
#from IPython.display import set_matplotlib_formats
#set_matplotlib_formats('png')
plt.style.use("seaborn-v0_8-whitegrid")
#locale.setlocale(locale.LC_ALL, 'de_DE.utf8')
# plt.rcdefaults()
#plt.rcParams['axes.formatter.use_locale'] = True
plt.rcParams["axes.formatter.use_mathtext"] = True

# =============================================================================
# Linienplot Property variables
# =============================================================================
figsize = (16,7)
plt.rcParams['savefig.dpi'] = 600 # Setting the resolution for saving files
plt.rcParams["figure.dpi"] = 100 # Setting resolution for figures displayes in IDE

color_cycler_left = cycle(['#FF0000', '#988ED5', '#2AA198', '#FBC15E', '#999999'])
colors_left = ['#FF0000', '#988ED5', '#2AA198', '#FBC15E', '#999999']
color_cycler_right = cycle(['#348ABD','#8EBA42', '#777777',  '#EE7600', '#C71585'])
colors_right = ['#348ABD','#8EBA42', '#777777',  '#EE7600', '#C71585']



[docs] def set_resolution(reso): """ Sets the resolution of the plot file when saved Parameters ---------- reso : int Integer for the dpi of the plot file to be saved (dpi). Returns ------- None. """ plt.rcParams['savefig.dpi'] = reso return
[docs] def set_plot_style(style): """ Sets the plot style using: plt.style.use("style") Parameters ---------- style : String A plot style that can be found in the path. Default is "seaborn-v0_8-whitegrid" Returns ------- None. """ plt.style.use(style) return
[docs] def Lineplot(data_left, data_right=None, title="Title", ylim1=None, ylim2=None, ylabel=["left","right"], fmt = "png", save_dir=None, aux_lines=None, x_rotation=45): """ Parameters ---------- data_left : DataFrame DESCRIPTION. Needs to have a DateTimeIndex data_right : TYPE, optional Needs to have a DateTimeIndex. title : String, optional Title for the plot. Is also used for the filename. So no '\\' or '.' etc allowed ylim1 : tuple, list with len==2 or an int, float, optional Parameter to be passed to ax.set_ylim(). Either a tuple, list with len==2 or an int, float, or None allowed. The default is None. ylim1 : tuple, list with len==2 or an int, float, optional Parameter to be passed to ax.set_ylim(). Either a tuple, list with len==2 or an int, float, or None allowed. The default is None. ylabel : String, optional Tuple or list with len==2 to be used for left and right label. The default is ["left","right"]. fmt : String or list of Strings, optional Format(s) to be used in savefig. If an iterable of strings is passed, the figure will be saved in all formats. The default is "png". save_dir : String, optional The directory in which to save the figure. If path is None, the figure will not be saved. The default is None. aux_lines : pd.DataFrame, pd.Series, list or dict, optional This will plot vertical or horizontal lines into the plot. Lines that have a numeric value will be treated as horizontal lines (of the left axis), lines that have a Datetime locator will be plotted as vertical lines. Wrong types will be ignored and a Warning will be printed. The default is None. x_rotation : int, float, optional Degress to rotate x-axis labels (dates). The default is 45. Returns ------- fig : matplotlib.Figure Figure object of the created plot ax1: matplotlib.Axes Axes (left axis) object of the created plot """ fontsize_ticks = 14 fontsize_labels = 18 font= font_manager.FontProperties(family="Arial", style="normal", size=fontsize_ticks) fig, ax1 = plt.subplots(figsize=figsize) lw = 1 color_cycler_left = cycle(colors_left) color_cycler_right = cycle(colors_right) for col in pd.DataFrame(data_left): ax1.plot(pd.DataFrame(data_left)[col],linewidth=lw, label=col, color = next(color_cycler_left)) major_grid_color="grey" # ax1.tick_params(axis="x", labelsize=fontsize_ticks) #Größe Achsenbeschriftung ändern # ax1.tick_params(axis="y", labelsize=fontsize_ticks, length=4, color="grey") ax1.tick_params(axis="both", labelsize=fontsize_ticks, length=4, color=major_grid_color) ax1.set_facecolor("white") ax1.xaxis.grid(True, which='minor') # ax1.tick_params(axis='x',which='major', length=4, color='grey', rotation=x_rotation, ha='right') # ax1.tick_params(axis='x', which='minor', rotation=x_rotation, ha="right") ax1.xaxis.grid(True, color=major_grid_color, which='major') ax1.yaxis.grid(True, color=major_grid_color) # plt.xticks(rotation=90, fontname="Arial") # ax1.tick_params(axis="x", labelsize=fontsize_ticks) #Größe Achsenbeschriftung ändern if data_right is not None: ax2= ax1.twinx() for col in pd.DataFrame(data_right): ax2.plot(pd.DataFrame(data_right)[col],linewidth=lw, label=col, color = next(color_cycler_right)) ax2.yaxis.grid(True, color=major_grid_color) ax2.tick_params(axis="y", labelsize=fontsize_ticks, length=4, color=major_grid_color) # ============================================================================= # Block to find an appropiate time scale on the x-axis # ============================================================================= start_time = data_left.index[0] end_time = data_left.index[-1] if data_right is not None: start_time = min(data_right.index[0], start_time) end_time = max(data_right.index[-1], end_time) plot_utils.scale_datetime_axis(ax1, start_time, end_time) ax1.set_xticks(ax1.get_xticks(), ax1.get_xticklabels(), rotation=x_rotation, ha="right") plt.setp(ax1.xaxis.get_minorticklabels(), rotation=x_rotation, ha="right") # ============================================================================= # End of Block for xaxis-Setup # # Block for y labels, ticks and legend # ============================================================================= plt.yticks(fontname="Arial") ax1.set_ylabel(ylabel[0], rotation=90, fontsize=fontsize_labels, fontname="Arial") #, color = "#00688B" ax1.set_ylim(ylim1) if data_right is not None: ax2.set_ylim(ylim2) ax2.set_ylabel(ylabel[1], rotation=90, fontsize=fontsize_labels, fontname="Arial")#, color = "#8B0000") ax1.set_title(title, fontsize=fontsize_labels, fontname="Arial") plt.subplots_adjust(top=0.966, bottom=0.220, left=0.097, right=0.977, hspace=0.2, wspace=0.2) #320 #shrink current axis to fit the legend below the plot box = ax1.get_position () ax1.set_position([box.x0 , box.y0 + 0.1 , box.width *0.9, box.height*0.8]) # Add the legend(s) below the plot leg1 = ax1.legend(bbox_to_anchor=(0.5 , -0.25), loc = "upper center",fancybox=True, shadow=True, ncol=7, fontsize = fontsize_ticks) #-045 und 065 if data_right is not None: leg2 = ax2.legend(bbox_to_anchor=(0.5 , -0.35), loc = "upper center",fancybox=True, shadow=True, ncol=7, fontsize = fontsize_ticks) # ============================================================================= # End of legend and ylabel BLock # # Block to add vertical or horizontal lines # ============================================================================= if isinstance(aux_lines, (pd.DataFrame, pd.Series, list, dict)): if isinstance(aux_lines, dict): aux_lines = pd.Series(aux_lines) elif isinstance(aux_lines, list): aux_lines = pd.Series("", index=aux_lines) #ckeck if horizontal or vertical lines (vertical only if index is of DateTimeFormat) # if aux_lines.index.is_numeric(): if pd.api.types.is_any_real_numeric_dtype(aux_lines.index): # lines are horizontal and based on the y-axis for key,val in aux_lines.items(): ax1.axhline(y = key, color = "#000000", linewidth=1, linestyle= "--", label = val) alignment = "left" if data_right is not None: alignment ="right" ax1.text(ax1.get_xlim()[1], key, val, fontsize=fontsize_ticks, ha=alignment) # if not isinstance(aux_lines.index, pd.DateTimeIndex): else: try: aux_lines.index = pd.to_datetime(aux_lines.index, dayfirst=True) for key,val in aux_lines.items(): x_min, x_max = (ax1.get_xlim()) if mdates.date2num(key) > x_min and mdates.date2num(key) < x_max: plt.axvline(x = key, color = "#000000", linewidth=1, linestyle= "--", label = val) ax1.text(key, ax1.get_ylim()[1], val, rotation=45, fontsize=fontsize_ticks) except SystemError: print("Lines trying to be plotted neither have a numeric locator nor can be translated into datetime objects\ndtype : "+str(aux_lines.index.dtype)+"\nExample: "+str(aux_lines.index[0])) # ============================================================================= # End of Block # ============================================================================= # ============================================================================= # Saving the figure # ============================================================================= save_title = title.replace("$","").strip('\\').replace("\\", "") if save_dir is not None: if plot_utils.is_iterable_of_strings(fmt): for f in fmt: plt.savefig(save_dir+"\\"+save_title+"."+f, bbox_inches="tight") else: plt.savefig(save_dir+"\\"+save_title+"."+fmt, bbox_inches="tight") else: pass # plt.savefig(save_title+"."+fmt) plt.show() return fig, ax1
[docs] def bar_plot(data_set,*, data_right=None, ylabel = None, title=None, xlabel=None, legend=True, colors=None, ax = None): """ Creates a bar plot with grouped bars. Each column of the data_set represents a bar per group, Each row represents a new group. Row index is used as xlabel Not complete for right axis plot Parameters ---------- data_set : DataFrame DataFrame with the bar data. Each column represents one bar, each row is a new bar group. * : Forcing following parameters to be named DESCRIPTION. ylabel : String, optional Label for y-axis. The default is 'mass flow in kg/s'. title : String, optional Plot title. The default is 'mass flow distribution'. legend : boolean, optional if a legend is shown. The default is True. colors : arrays, optional array representing the color for each bar. The default is None. ax : matplotlib Axes, optional Can be passed to plot on an existing axis. The default is None. Returns ------- ax : Axes Returns Axes object. plt.gcf() Current figure of matplotlib. """ if ax == None: fig, ax = plt.subplots(figsize=figsize) axs = ax num_bars = len(data_set.columns) #number of bars per group if data_right is not None: num_bars = num_bars + len(data_right.columns) # Adding number of bars from the right y-axis x = np.arange(len(data_set.index))#label locations, dependent on number of rows of dataframe major_grid_color = "grey" ax.tick_params(axis="both", length=4, color=major_grid_color) ax.xaxis.grid(False) ax.yaxis.grid(True, color = major_grid_color) # Handling the bar color if colors is None: colors = cycle(plt.rcParams['axes.prop_cycle'].by_key()['color']) elif type(colors)==str: colors = cycle([colors]) # Doing the bar position width = 1/(num_bars+2) #width of the bars i=0 position = num_bars//2 offset = 0 legend_loc = "best" if num_bars%2 == 0: #if number of bars is even, bars need to be offset by half a width offset = width/2 for label in data_set.columns: if hasattr(colors, "__getitem__"): ax.bar(x-width*position+offset, data_set[label], width, label=label, color=colors[i]) else: ax.bar(x-width*position+offset, data_set[label], width, label=label, color=next(colors)) i= i+1 position = position-1 ax.set_xticks(x) if data_right is not None: ax2 = ax.twinx() ax2.yaxis.grid(False) ax2.tick_params(axis="y", length=4, color=major_grid_color) for label in data_right.columns: if hasattr(colors, "__getitem__"): ax2.bar(x-width*position+offset, data_right[label], width, label=label, color=colors[i]) else: ax2.bar(x-width*position+offset, data_right[label], width, label=label, color=next(colors)) column_maxes=data_right.max(axis=1, skipna=True) total_max = column_maxes.max() ax2.set_ylim(None, total_max*1.4) if legend: ax2.legend(loc="upper right") legend_loc = "upper left" axs = [ax, ax2] if title is not None: ax.set_title(title) if xlabel is not None: ax.set_xlabel(xlabel)# if ylabel is not None: if type(ylabel) == str: ax.set_ylabel(ylabel) elif hasattr(ylabel, "__getitem__"): #Checking if it is Indexable (e.g. List, ... with multiple labels were passed) ax.set_ylabel(ylabel[0]) if data_right is not None: ax2.set_ylabel(ylabel[1]) else: ax.set_ylabel() ax.set_xticklabels(data_set.index) #find max value in whole frame column_maxes=data_set.max(axis=1, skipna=True) total_max = column_maxes.max() ax.set_ylim(None, total_max*1.3) if legend: ax.legend(loc=legend_loc) return axs, plt.gcf()
# Finally an example
[docs] def example(): """ Run examples of the functions defined in this module. There are two ways of using this example: - import sattoolbox as stb and call stb.plots.fm_plots.example() - directly run this file like a script (this works, because "if __name__ == '__main__':" runs this example) Returns ------- None. """ # ============================================================================= # Line Plot Example # ============================================================================= print("Testing Linienplot...") print("Creating data") x = np.linspace(-np.pi, np.pi, 8760) y_year = np.sin(x) y_day = np.sin(x*365) hours = pd.date_range(start="2023-01-01", end="2023-12-31 23:00", freq="1h") T_data = pd.DataFrame(data={ "T_1": 15+15*y_year+1*y_day, "T_2": 12+10*y_year+1*y_day, "T_3": 20+2*y_year+2*y_day, "T_4": 15+10*(y_year+1000)+5*y_day }, index=hours) phi_data = pd.DataFrame(data={ r'$\varphi_{\mathrm{1}}$': 60+30*y_year+5*y_day, r'$\varphi_{\mathrm{2}}$': 50+40*y_year+1*y_day, r'$\varphi_{\mathrm{3}}$': 80+7*y_year+2*y_day, }, index=hours) print("Data creation complete") figsize=(10,6) print("Creating Example plots") Lineplot(T_data, phi_data, title="Title for Lineplot Diagram", ylim1=[-10,40], ylim2=[0,100], ylabel=["Temperature in °C", "Rel. Humidity in %"], aux_lines={35:"line at 35°C"}) Lineplot(T_data.iloc[:200], phi_data[100:450], title="Title for Lineplot Diagram", ylim1=[-10,40], ylim2=[0,100], ylabel=["Temperature in °C", "Rel. Humidity in %"], aux_lines={"15.01.2023":"15th of Jan."}) Lineplot(T_data.iloc[:2000], phi_data[500:2000], title="Title for Lineplot Diagram", ylim1=[-10,40], ylim2=[0,100], ylabel=["Temperature in °C", "Rel. Humidity in %"], aux_lines={"15.01.2023":"15th of Jan."}) Lineplot(T_data.iloc[:5000], phi_data[500:5000], title="Title for Lineplot Diagram", ylim1=[-10,40], ylim2=[0,100], ylabel=["Temperature in °C", "Rel. Humidity in %"], aux_lines={"15.01.2023":"15th of Jan.","10.07.2023":"10th of Jul."}) # ============================================================================= # Bar plot Examples # ============================================================================= print("--------------------------------------") print("Testing Bar plot") print("creating data") # data = pd.read_csv("testdata\\bar_plot.csv", sep=";", decimal=".", index_col=0) data_dict = {"Left1":[100,300,534,45,321,5,458,240,100,356], "Left2":[245,42,13,462,697,645,352,243,345,65], "Left3": [100,300,534,45,321,5,458,240,100,356], "Left4": [245,42,13,462,697,645,352,43,345,65], "Left5": [100,300,534,45,321,5,458,240,100,356], "Left6": [245,42,13,462,697,645,352,243,345,65], "Right1": [1,0,0,1,1,0.5,0.5,1,0,1]} data =pd.DataFrame(data_dict) print("Data creation complete") print("Plotting with 2 Values per group and default settings") bar_plot(data[["Left1", "Left2"]]) print("-----------") print("Plotting with 6 values per group and Labels in black") bar_plot(data[["Left1", "Left2","Left3", "Left4","Left5", "Left6"]], ylabel="Y-Label", xlabel= "X-Label", title="Title", colors="black") print("--------------") print("Plotting bar plot with 2nd yaxis") bar_plot(data[["Left1", "Left2"]], data_right=data[["Right1"]], ylabel=["Ylabel Left", "Ylabel right"])