# -*- 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"])