mirror of
https://github.com/FAUSheppy/ths-datenlogger
synced 2025-12-07 20:31:35 +01:00
Initial
This commit is contained in:
231
plot_graphutils.py
Normal file
231
plot_graphutils.py
Normal file
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/python3
|
||||
from config_parse import CFG
|
||||
from datetime import datetime, timedelta
|
||||
import matplotlib
|
||||
matplotlib.use(CFG("use_gui_backend"))
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates
|
||||
import matplotlib.ticker as ticker
|
||||
from constants import *
|
||||
import math
|
||||
import plot_timeutils
|
||||
matplotlib.rc('font', **GLOBAL_FONT)
|
||||
|
||||
def getlimits_y(y):
|
||||
ymax = max(y)+CFG("empty_space_above_plot")
|
||||
y_min_height = CFG("yaxis_minnimum_hight")
|
||||
if y_min_height != 0 and y_min_height > ymax:
|
||||
ymax = y_min_height
|
||||
y_start_val = CFG("yaxis_start_value")
|
||||
if y_start_val < min(y) or CFG("yaxis_force_start_value"):
|
||||
ymin=y_start_val
|
||||
else:
|
||||
ymin=min(y)
|
||||
return (ymin,ymax)
|
||||
|
||||
def avg(array):
|
||||
return sum(array)/float(len(array))
|
||||
|
||||
def legend_box_contents(name,y):
|
||||
if CFG("show_min"):
|
||||
name += " min: %.1f,"%min(y)
|
||||
if CFG("show_max"):
|
||||
name += " max: %.1f,"%max(y)
|
||||
if CFG("show_avg"):
|
||||
name += " Mittelwert: %.1f,"% avg(y)
|
||||
return name.rstrip(",")
|
||||
|
||||
def general_background_setup(tup,ymin,ymax,x):
|
||||
|
||||
unix_x = list(map(plot_timeutils.unix,x))
|
||||
|
||||
### SET AXIS LIMITS ###
|
||||
tup[AXIS].set_ylim([ymin,ymax])
|
||||
tup[AXIS].set_xlim([plot_timeutils.unix(min(x)),plot_timeutils.unix(max(x))])
|
||||
|
||||
if CFG("draw_thresholds"):
|
||||
hcrit=CFG("humidity_critical")
|
||||
hwarn=CFG("humidity_warning")
|
||||
tlow=CFG("acceptable_temp_low")
|
||||
thigh=CFG("acceptable_temp_high")
|
||||
tup[AXIS].axhline(y=CFG("target_temperatur"),ls=CFG("hline_line_style"),lw=CFG("hline_line_width"),color=CFG("acceptable_temp_color"))
|
||||
tup[AXIS].axhline(y=hcrit,ls=CFG("hline_line_style"),lw=CFG("hline_line_width"),color=CFG("humidity_crit_color"))
|
||||
tup[AXIS].axhspan(hwarn,hcrit,color=CFG("humidity_warning_color"),alpha=CFG("humidity_warning_alpha"))
|
||||
tup[AXIS].axhspan(hcrit,ymax,color=CFG("humidity_crit_color"),alpha=CFG("humidity_crit_alpha"))
|
||||
tup[AXIS].axhspan(tlow,thigh,color=CFG("acceptable_temp_color"),alpha=CFG("acceptable_temp_alpha"))
|
||||
|
||||
#### GRID ####
|
||||
major_xticks = gen_xticks_from_timeseries(x)
|
||||
minor_xticks = get_minor_xticks_from_major(major_xticks)
|
||||
if CFG("raster"):
|
||||
grid(tup,major_xticks,ymin,ymax)
|
||||
|
||||
#### XTICKS ####
|
||||
tup[AXIS].set_xticks(major_xticks)
|
||||
tup[AXIS].xaxis.set_major_formatter(ticker.FuncFormatter(xlabel_formater_callback))
|
||||
tup[AXIS].xaxis.set_major_locator(ticker.FixedLocator(major_xticks, nbins=None))
|
||||
tup[AXIS].xaxis.set_minor_locator(ticker.FixedLocator(minor_xticks, nbins=None))
|
||||
tup[AXIS].xaxis.set_tick_params(which='minor',width=0.2,direction="out")
|
||||
|
||||
tup[AXIS].yaxis.set_major_locator(ticker.MultipleLocator(CFG("y_tick_interval")))
|
||||
tup[AXIS].yaxis.set_minor_locator(ticker.MultipleLocator(1))
|
||||
tup[AXIS].yaxis.set_tick_params(which='minor',width=0.2,direction="out")
|
||||
|
||||
tup[AXIS].tick_params(axis='x',which="major",labelsize=CFG("xticks_font_size"));
|
||||
tup[AXIS].tick_params(axis='y',which="major",labelsize=CFG("yticks_font_size"));
|
||||
|
||||
## ROTATION XLABELS ##
|
||||
rotation=CFG("xticks_label_degree")
|
||||
if rotation > 0:
|
||||
plt.xticks(rotation=rotation,ha='right')
|
||||
|
||||
## AXIS LABELS
|
||||
ylabel_box = dict(boxstyle="square",facecolor='grey', alpha=0.4, edgecolor='black',lw=0.5)
|
||||
xlabel_box = ylabel_box
|
||||
label_size = 6
|
||||
spacing=0.1
|
||||
tup[AXIS].set_ylabel(CFG("y_label"),rotation='horizontal',size=label_size,bbox=ylabel_box)
|
||||
tup[AXIS].yaxis.set_label_coords(0.055,0.95)
|
||||
tup[AXIS].set_xlabel(CFG("x_label"),size=label_size,bbox=xlabel_box)
|
||||
tup[AXIS].xaxis.set_label_coords(0.925,0.05)
|
||||
|
||||
## GENERAL LEGEND ##
|
||||
legend_handle = tup[AXIS].legend(
|
||||
loc=CFG("legend_location"),
|
||||
edgecolor="inherit",
|
||||
fancybox=False,
|
||||
borderaxespad=spacing,
|
||||
prop={'family': 'monospace','size':CFG("legend_font_size")}
|
||||
)
|
||||
legend_handle.get_frame().set_linewidth(0.2)
|
||||
tup[AXIS].set_aspect(get_aspect_ratio(unix_x,ymin,ymax,major_xticks))
|
||||
|
||||
|
||||
def get_aspect_ratio(ux,ymin,ymax,xticks):
|
||||
ratio = 100
|
||||
tmp = CFG("aspect_ratio")
|
||||
if str(tmp) == "A4":
|
||||
ratio = a4_aspect()
|
||||
else:
|
||||
ratio=tmp
|
||||
magic_value = 3.25
|
||||
return ratio * ( max(ux) - min(ux) ) / float(ymax - ymin + magic_value)
|
||||
|
||||
def a4_aspect():
|
||||
return 1/math.sqrt(2)
|
||||
|
||||
def grid(tup,xticks,ymin,ymax):
|
||||
lw = CFG("grid_line_width")
|
||||
ls = CFG("grid_line_style")
|
||||
color = CFG("grid_line_color")
|
||||
hour_mul = 24
|
||||
expected_vlines = len(list(filter(lambda xt: xt%3600 < 60,xticks)))
|
||||
safety_first = 60*60 +10
|
||||
step = xticks[1]-xticks[0]
|
||||
if step < (24*3600)-safety_first:
|
||||
if expected_vlines <= 6:
|
||||
hour_mul = 1
|
||||
elif expected_vlines <=12:
|
||||
hour_mul = 2
|
||||
elif expected_vlines <=24:
|
||||
hour_mul = 4
|
||||
|
||||
for xt in xticks:
|
||||
leck_mich = datetime.fromtimestamp(xt)
|
||||
if leck_mich.hour == leck_mich.minute == leck_mich.second == 0:
|
||||
tup[AXIS].axvline(xt,ls="-",lw=CFG("major_line_width"),color=color)
|
||||
else:
|
||||
tup[AXIS].axvline(xt,ls=ls,lw=lw,color=color)
|
||||
## HLINES ##
|
||||
y_interval = CFG("raster_hline_prefered_interval")
|
||||
cur = ymin
|
||||
while cur < ymax:
|
||||
cur += y_interval
|
||||
tup[AXIS].axhline(cur,ls=ls,lw=lw,color=color)
|
||||
|
||||
def find_step(step,x,total_xticks):
|
||||
intervals = parse_possible_intervals()
|
||||
start = min(x)
|
||||
if CFG("always_allow_days_as_xticks") and step > timedelta(days=1)/2:
|
||||
step = timedelta(days=round(step.days+1))
|
||||
start = min(x).replace(hour=0,second=0,minute=0)
|
||||
return (start,step)
|
||||
|
||||
min_delta_step = timedelta(days=1) # the actual step that has the lowest delta
|
||||
min_delta = timedelta(days=1000) # the delta o thus step
|
||||
for s in intervals:
|
||||
delta = max(s,step)-min(s,step)
|
||||
if delta < min_delta:
|
||||
min_delta_step = s
|
||||
min_delta = delta
|
||||
|
||||
step = min_delta_step
|
||||
start = plot_timeutils.round_time_to_step(start,step)
|
||||
|
||||
warn_on_too_much_xticks(x,total_xticks,step)
|
||||
return (start,step)
|
||||
|
||||
def parse_possible_intervals():
|
||||
intervals = CFG("acceptable_x_intervals")
|
||||
parsed_intervals = []
|
||||
for s in intervals.split(','):
|
||||
try:
|
||||
st = int(s[:-1])
|
||||
except ValueError:
|
||||
raise ValueError("'acceptable_x_intervals' muss die Form 'Zahl[s(econds),m(minutes),h(ours),d(days)]' haben!")
|
||||
except Exception:
|
||||
raise ValueError("invalid intervals for x_labels %s [index out of bounds], did you write something like this ',,,,' ?]"%str(intervals))
|
||||
if s.endswith("s"):
|
||||
if 60 % st != 0:
|
||||
raise ValueError("interval must fit to next bigger interval so basicly for hours 24%interval==0")
|
||||
parsed_intervals += [timedelta(seconds=st)]
|
||||
elif s.endswith("m"):
|
||||
if 60 % st != 0:
|
||||
raise ValueError("interval must fit to next bigger interval so basicly for hours 24%interval==0")
|
||||
parsed_intervals += [timedelta(minutes=st)]
|
||||
elif s.endswith("h"):
|
||||
if 24 % st != 0:
|
||||
raise ValueError("interval must fit to next bigger interval so basicly for hours 24%interval==0")
|
||||
parsed_intervals += [timedelta(hours=st)]
|
||||
elif s.endswith("d"):
|
||||
parsed_intervals += [timedelta(days=st)]
|
||||
else:
|
||||
raise ValueError("invalide Zeitspezifizierer in %s (muss, s,m,h oder d sein)"%str(intervals))
|
||||
return parsed_intervals
|
||||
|
||||
def warn_on_too_much_xticks(x,total_xticks,step):
|
||||
if (max(x)-min(x))/step > 2*total_xticks:
|
||||
print("Warnung: maximales xinterval zu niedrig eine sinnvolle Anzahl an xticks zu generieren (total x_ticks: %d"%total_xticks)
|
||||
|
||||
def get_minor_xticks_from_major(major):
|
||||
mult = CFG("minor_xticks_per_major")
|
||||
step = (major[1]-major[0])/mult
|
||||
ret = []
|
||||
for x in major:
|
||||
if x == max(major):
|
||||
break
|
||||
ret += [x+ 0*step]
|
||||
ret += [x+ 1*step]
|
||||
ret += [x+ 2*step]
|
||||
ret += [x+ 3*step]
|
||||
ret += [x+ 4*step]
|
||||
return ret
|
||||
|
||||
def gen_xticks_from_timeseries(x):
|
||||
ticks=CFG("prefered_total_xticks")
|
||||
xmin = min(x)
|
||||
xmax = max(x)
|
||||
delta = xmax-xmin
|
||||
step = delta/ticks
|
||||
cur,step = find_step(step,x,ticks)
|
||||
xticks = []
|
||||
xmax += step*CFG("add_x_labels_at_end")
|
||||
while cur < xmax:
|
||||
xticks += [plot_timeutils.unix(cur)]
|
||||
cur+=step
|
||||
return xticks
|
||||
|
||||
def xlabel_formater_callback(tick_val, tick_pos):
|
||||
dt = datetime.fromtimestamp(tick_val)
|
||||
tformat = CFG("timeformat_x_axis").replace('$','%')
|
||||
return dt.strftime(tformat)
|
||||
Reference in New Issue
Block a user