UPDATE report & graph libraries for standalone test-report generation

Signed-off-by: anil-tegala <anil.tegala@candelatech.com>
This commit is contained in:
anil-tegala
2024-04-23 03:56:35 +05:30
parent a3e6a505e9
commit b2e3c103bf
2 changed files with 822 additions and 43 deletions

View File

@@ -24,10 +24,26 @@ import matplotlib.pyplot as plt
import numpy as np import numpy as np
import pdfkit import pdfkit
from matplotlib.colors import ListedColormap from matplotlib.colors import ListedColormap
import matplotlib.ticker as mticker
import argparse import argparse
import traceback
import logging
# TODO have scipy be part of the base install
try:
from scipy import interpolate
except Exception as x:
print("Info: scipy package not installed, Needed for smoothing linear plots 'pip install scipy' ")
traceback.print_exception(Exception, x, x.__traceback__, chain=True)
sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../"))) sys.path.append(os.path.join(os.path.abspath(__file__ + "../../../")))
logger = logging.getLogger(__name__)
lf_logger_config = importlib.import_module("py-scripts.lf_logger_config")
lf_csv = importlib.import_module("py-scripts.lf_csv") lf_csv = importlib.import_module("py-scripts.lf_csv")
lf_csv = lf_csv.lf_csv lf_csv = lf_csv.lf_csv
@@ -57,6 +73,7 @@ class lf_bar_graph:
_xaxis_step=1, _xaxis_step=1,
_xticks_font=None, _xticks_font=None,
_xaxis_value_location=0, _xaxis_value_location=0,
_xticks_rotation=None,
_text_font=None, _text_font=None,
_text_rotation=None, _text_rotation=None,
_grp_title="", _grp_title="",
@@ -66,7 +83,10 @@ class lf_bar_graph:
_legend_ncol=1, _legend_ncol=1,
_legend_fontsize=None, _legend_fontsize=None,
_dpi=96, _dpi=96,
_enable_csv=False): _enable_csv=False,
_remove_border=None,
_alignment=None
):
if _data_set is None: if _data_set is None:
_data_set = [[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]] _data_set = [[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]]
@@ -107,6 +127,9 @@ class lf_bar_graph:
self.legend_box = _legend_box self.legend_box = _legend_box
self.legend_ncol = _legend_ncol self.legend_ncol = _legend_ncol
self.legend_fontsize = _legend_fontsize self.legend_fontsize = _legend_fontsize
self.remove_border = _remove_border
self.alignment = _alignment
self.xticks_rotation = _xticks_rotation
def build_bar_graph(self): def build_bar_graph(self):
if self.color is None: if self.color is None:
@@ -116,8 +139,17 @@ class lf_bar_graph:
self.color.append(self.color_name[i]) self.color.append(self.color_name[i])
i = i + 1 i = i + 1
plt.subplots(figsize=self.figsize) fig_size, ax = plt.subplots(figsize=self.figsize, gridspec_kw=self.alignment)
i = 0 i = 0
# to remove the borders
if self.remove_border is not None:
for border in self.remove_border:
ax.spines[border].set_color(None)
if 'left' in self.remove_border: # to remove the y-axis labeling
yaxis_visable =False
else:
yaxis_visable=True
ax.yaxis.set_visible(yaxis_visable)
def show_value(rectangles): def show_value(rectangles):
for rect in rectangles: for rect in rectangles:
@@ -148,10 +180,10 @@ class lf_bar_graph:
plt.xticks(np.arange(0, plt.xticks(np.arange(0,
len(self.xaxis_categories), len(self.xaxis_categories),
step=self.xaxis_step), step=self.xaxis_step),
fontsize=self.xticks_font) fontsize=self.xticks_font,rotation=self.xticks_rotation)
else: else:
plt.xticks([i + self._xaxis_value_location for i in np.arange(0, len(self.data_set[0]), step=self.xaxis_step)], plt.xticks([i + self._xaxis_value_location for i in np.arange(0, len(self.data_set[0]), step=self.xaxis_step)],
self.xaxis_categories, fontsize=self.xticks_font) self.xaxis_categories, fontsize=self.xticks_font,rotation=self.xticks_rotation)
plt.legend( plt.legend(
handles=self.legend_handles, handles=self.legend_handles,
loc=self.legend_loc, loc=self.legend_loc,
@@ -163,7 +195,7 @@ class lf_bar_graph:
plt.gcf() plt.gcf()
plt.savefig("%s.png" % self.graph_image_name, dpi=96) plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close() plt.close()
print("{}.png".format(self.graph_image_name)) logger.debug("{}.png".format(self.graph_image_name))
if self.enable_csv: if self.enable_csv:
if self.data_set is not None and self.xaxis_categories is not None: if self.data_set is not None and self.xaxis_categories is not None:
if len(self.xaxis_categories) == len(self.data_set[0]): if len(self.xaxis_categories) == len(self.data_set[0]):
@@ -179,11 +211,181 @@ class lf_bar_graph:
raise ValueError( raise ValueError(
"Length and x-axis values and y-axis values should be same.") "Length and x-axis values and y-axis values should be same.")
else: else:
print("No Dataset Found") logger.debug("No Dataset Found")
print("{}.csv".format(self.graph_image_name)) logger.debug("{}.csv".format(self.graph_image_name))
return "%s.png" % self.graph_image_name return "%s.png" % self.graph_image_name
class lf_bar_graph_horizontal:
def __init__(self, _data_set=None,
_xaxis_name="x-axis",
_yaxis_name="y-axis",
_yaxis_categories=None,
_yaxis_label=None,
_graph_title="",
_title_size=16,
_graph_image_name="image_name",
_label=None,
_color=None,
_bar_height=0.25,
_color_edge='grey',
_font_weight='bold',
_color_name=None,
_figsize=(10, 5),
_show_bar_value=False,
_yaxis_step=1,
_yticks_font=None,
_yaxis_value_location=0,
_yticks_rotation=None,
_text_font=None,
_text_rotation=None,
_grp_title="",
_legend_handles=None,
_legend_loc="best",
_legend_box=None,
_legend_ncol=1,
_legend_fontsize=None,
_dpi=96,
_enable_csv=False,
_remove_border=None,
_alignment=None
):
if _data_set is None:
_data_set = [[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]]
if _yaxis_categories is None:
_yaxis_categories = [1, 2, 3, 4]
if _yaxis_label is None:
_yaxis_label = ["a", "b", "c", "d"]
if _label is None:
_label = ["bi-downlink", "bi-uplink", 'uplink']
if _color_name is None:
_color_name = ['lightcoral', 'darkgrey', 'r', 'g', 'b', 'y']
self.data_set = _data_set
self.xaxis_name = _xaxis_name
self.yaxis_name = _yaxis_name
self.yaxis_categories = _yaxis_categories
self.yaxis_label = _yaxis_label
self.title = _graph_title
self.title_size = _title_size
self.graph_image_name = _graph_image_name
self.label = _label
self.color = _color
self.bar_height = _bar_height
self.color_edge = _color_edge
self.font_weight = _font_weight
self.color_name = _color_name
self.figsize = _figsize
self.show_bar_value = _show_bar_value
self.yaxis_step = _yaxis_step
self.yticks_font = _yticks_font
self._yaxis_value_location = _yaxis_value_location
self.text_font = _text_font
self.text_rotation = _text_rotation
self.grp_title = _grp_title
self.enable_csv = _enable_csv
self.lf_csv = lf_csv()
self.legend_handles = _legend_handles
self.legend_loc = _legend_loc
self.legend_box = _legend_box
self.legend_ncol = _legend_ncol
self.legend_fontsize = _legend_fontsize
self.remove_border = _remove_border
self.alignment = _alignment
self.yticks_rotation = _yticks_rotation
def build_bar_graph_horizontal(self):
if self.color is None:
i = 0
self.color = []
for _ in self.data_set:
self.color.append(self.color_name[i])
i = i + 1
fig_size, ax = plt.subplots(figsize=self.figsize, gridspec_kw=self.alignment)
i = 0
# to remove the borders
if self.remove_border is not None:
for border in self.remove_border:
ax.spines[border].set_color(None)
if 'left' in self.remove_border: # to remove the y-axis labeling
yaxis_visable =False
else:
yaxis_visable=True
ax.yaxis.set_visible(yaxis_visable)
def show_value(rectangles):
for rect in rectangles:
w = rect.get_width()
y = rect.get_y()
h = rect.get_height()
x = rect.get_x()
# adding 1 may not always work based on the x axis scale may need to be configurable
plt.text(w + 1 , rect.get_y() + rect.get_height() / 4., w,
ha='center', va='bottom', rotation=self.text_rotation, fontsize=self.text_font)
for _ in self.data_set:
if i > 0:
br = br1
br2 = [y + self.bar_height for y in br]
rects = plt.barh(br2, self.data_set[i], color=self.color[i], height=self.bar_height,
edgecolor=self.color_edge, label=self.label[i])
if self.show_bar_value:
show_value(rects)
br1 = br2
i = i + 1
else:
br1 = np.arange(len(self.data_set[i]))
rects = plt.barh(br1, self.data_set[i], color=self.color[i], height=self.bar_height,
edgecolor=self.color_edge, label=self.label[i])
if self.show_bar_value:
show_value(rects)
i = i + 1
plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15)
plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15)
if self.yaxis_categories[0] == 0:
plt.yticks(np.arange(0,
len(self.yaxis_categories),
step=self.yaxis_step),
fontsize=self.yticks_font,rotation=self.yticks_rotation)
else:
plt.yticks([i + self._yaxis_value_location for i in np.arange(0, len(self.data_set[0]), step=self.yaxis_step)],
self.yaxis_categories, fontsize=self.yticks_font,rotation=self.yticks_rotation)
plt.legend(
handles=self.legend_handles,
loc=self.legend_loc,
bbox_to_anchor=self.legend_box,
ncol=self.legend_ncol,
fontsize=self.legend_fontsize)
plt.suptitle(self.title, fontsize=self.title_size)
plt.title(self.grp_title)
plt.gcf()
plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close()
logger.debug("{}.png".format(self.graph_image_name))
if self.enable_csv:
if self.data_set is not None and self.yaxis_categories is not None:
if len(self.yaxis_categories) == len(self.data_set[0]):
self.lf_csv.columns = []
self.lf_csv.rows = []
self.lf_csv.columns.append(self.yaxis_name)
self.lf_csv.columns.extend(self.label)
self.lf_csv.rows.append(self.yaxis_categories)
self.lf_csv.rows.extend(self.data_set)
self.lf_csv.filename = f"{self.graph_image_name}.csv"
self.lf_csv.generate_csv()
else:
raise ValueError(
"Length and x-axis values and y-axis values should be same.")
else:
logger.debug("No Dataset Found")
logger.debug("{}.csv".format(self.graph_image_name))
return "%s.png" % self.graph_image_name
class lf_scatter_graph: class lf_scatter_graph:
def __init__(self, def __init__(self,
_x_data_set=None, _x_data_set=None,
@@ -254,7 +456,7 @@ class lf_scatter_graph:
plt.legend(handles=scatter.legend_elements()[0], labels=self.label) plt.legend(handles=scatter.legend_elements()[0], labels=self.label)
plt.savefig("%s.png" % self.graph_image_name, dpi=96) plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close() plt.close()
print("{}.png".format(self.graph_image_name)) logger.debug("{}.png".format(self.graph_image_name))
if self.enable_csv: if self.enable_csv:
self.lf_csv.columns = self.label self.lf_csv.columns = self.label
self.lf_csv.rows = self.y_data_set self.lf_csv.rows = self.y_data_set
@@ -263,8 +465,267 @@ class lf_scatter_graph:
return "%s.png" % self.graph_image_name return "%s.png" % self.graph_image_name
# have a second yaxis with line graph
class lf_bar_line_graph:
def __init__(self,
_data_set1=None,
# Note data_set2, data_set2_poly and data_set2_spline needs same size list
_data_set2=None,
_data_set2_poly=[False], # Values are True or False
_data_set2_poly_degree=[3],
_data_set2_interp1d=[False], # Values are True or False
_xaxis_name="x-axis",
_y1axis_name="y1-axis",
_y2axis_name="y2-axis",
_xaxis_categories=None,
_xaxis_label=None,
_graph_title="",
_title_size=16,
_graph_image_name="image_name",
_label1=None,
_label2=None,
_label2_poly=None,
_label2_interp1d=None,
_color1=None,
_color2=None,
_color2_poly=None,
_color2_interp1d=None,
_bar_width=0.25,
_color_edge='grey',
_font_weight='bold',
_color_name1=None,
_color_name2=None,
_marker=None,
_figsize=(10, 5),
_show_bar_value=False,
_xaxis_step=1,
_xticks_font=None,
_xaxis_value_location=0,
_text_font=None,
_text_rotation=None,
_grp_title="",
_legend_handles=None,
_legend_loc1="best",
_legend_loc2="best",
_legend_box1=None,
_legend_box2=None,
_legend_ncol=1,
_legend_fontsize=None,
_dpi=96,
_enable_csv=False):
if _data_set1 is None:
_data_set1 = [[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]]
if _xaxis_categories is None:
_xaxis_categories = [1, 2, 3, 4]
if _xaxis_label is None:
_xaxis_label = ["a", "b", "c", "d"]
if _label1 is None:
_label1 = ["bi-downlink", "bi-uplink", 'uplink']
if _label2 is None:
_label2 = ["bi-downlink", "bi-uplink", 'uplink']
if _color_name1 is None:
_color_name1 = ['lightcoral', 'darkgrey', 'r', 'g', 'b', 'y']
if _color_name2 is None:
_color_name2 = ['lightcoral', 'darkgrey', 'r', 'g', 'b', 'y']
self.data_set1 = _data_set1
self.data_set2 = _data_set2
self.data_set2_poly = _data_set2_poly
self.data_set2_poly_degree = _data_set2_poly_degree
self.data_set2_interp1d = _data_set2_interp1d
self.xaxis_name = _xaxis_name
self.y1axis_name = _y1axis_name
self.y2axis_name = _y2axis_name
self.xaxis_categories = _xaxis_categories
self.xaxis_label = _xaxis_label
self.title = _graph_title
self.title_size = _title_size
self.graph_image_name = _graph_image_name
self.label1 = _label1
self.label2 = _label2
self.label2_poly = _label2_poly
self.label2_interp1d = _label2_interp1d
self.color1 = _color1
self.color2 = _color2
self.color2_poly = _color2_poly
self.color2_interp1d = _color2_interp1d
self.marker = _marker
self.bar_width = _bar_width
self.color_edge = _color_edge
self.font_weight = _font_weight
self.color_name1 = _color_name1
self.color_name2 = _color_name2
self.figsize = _figsize
self.show_bar_value = _show_bar_value
self.xaxis_step = _xaxis_step
self.xticks_font = _xticks_font
self._xaxis_value_location = _xaxis_value_location
self.text_font = _text_font
self.text_rotation = _text_rotation
self.grp_title = _grp_title
self.enable_csv = _enable_csv
self.lf_csv = lf_csv()
self.legend_handles = _legend_handles
self.legend_loc1 = _legend_loc1
self.legend_loc2 = _legend_loc2
self.legend_box1 = _legend_box1
self.legend_box2 = _legend_box2
self.legend_ncol = _legend_ncol
self.legend_fontsize = _legend_fontsize
def build_bar_line_graph(self):
if self.color1 is None:
i = 0
self.color1 = []
for _ in self.data_set1:
self.color1.append(self.color_name[i])
i = i + 1
fig, ax1 = plt.subplots(figsize=self.figsize)
ax2 = ax1.twinx()
i = 0
def show_value(rectangles):
for rect in rectangles:
h = rect.get_height()
ax1.text(rect.get_x() + rect.get_width() / 2., h, h,
ha='center', va='bottom', rotation=self.text_rotation, fontsize=self.text_font)
for _ in self.data_set1:
if i > 0:
br = br1
br2 = [x + self.bar_width for x in br]
rects = ax1.bar(br2, self.data_set1[i], color=self.color1[i], width=self.bar_width,
edgecolor=self.color_edge, label=self.label1[i])
if self.show_bar_value:
show_value(rects)
br1 = br2
i = i + 1
else:
br1 = np.arange(len(self.data_set1[i]))
rects = ax1.bar(br1, self.data_set1[i], color=self.color1[i], width=self.bar_width,
edgecolor=self.color_edge, label=self.label1[i])
if self.show_bar_value:
show_value(rects)
i = i + 1
ax1.set_xlabel(self.xaxis_name, fontweight='bold', fontsize=15)
ax1.set_ylabel(self.y1axis_name, fontweight='bold', fontsize=15)
if self.xaxis_categories[0] == 0:
xsteps = plt.xticks(np.arange(0,
len(self.xaxis_categories),
step=self.xaxis_step),
fontsize=self.xticks_font)
else:
xsteps = plt.xticks([i + self._xaxis_value_location for i in np.arange(0, len(self.data_set1[0]), step=self.xaxis_step)],
self.xaxis_categories, fontsize=self.xticks_font)
ax1.legend(
handles=self.legend_handles,
loc=self.legend_loc1,
bbox_to_anchor=self.legend_box1,
ncol=self.legend_ncol,
fontsize=self.legend_fontsize)
# overlay line graph
def show_value2(data):
for item, value in enumerate(data):
ax2.text(item, value, "{value}".format(value=value), ha='center',rotation=self.text_rotation, fontsize=self.text_font)
i = 0
for _ in self.data_set2:
br1 = np.arange(len(self.data_set2[i]))
ax2.plot(
br1,
self.data_set2[i],
color=self.color2[i],
label=self.label2[i],
marker=self.marker[i])
show_value2(self.data_set2[i])
# do polynomial smoothing
if self.data_set2_poly[i]:
poly = np.polyfit(br1,self.data_set2[i],self.data_set2_poly_degree[i])
poly_y = np.poly1d(poly)(br1)
ax2.plot(
br1,
poly_y,
color=self.color2_poly[i],
label=self.label2_poly[i]
)
if self.data_set2_interp1d[i]:
cubic_interpolation_model = interpolate.interp1d(br1, self.data_set2[i],kind="cubic")
x_sm = np.array(br1)
x_smooth = np.linspace(x_sm.min(), x_sm.max(), 500)
y_smooth = cubic_interpolation_model(x_smooth)
ax2.plot(
x_smooth,
y_smooth,
color=self.color2_interp1d[i],
label=self.label2_interp1d[i]
)
i += 1
ax2.set_xlabel(self.xaxis_name, fontweight='bold', fontsize=15)
ax2.set_ylabel(self.y2axis_name, fontweight='bold', fontsize=15)
ax2.tick_params(axis = 'y', labelcolor = 'orange')
ax2.legend(
handles=self.legend_handles,
loc=self.legend_loc2,
bbox_to_anchor=self.legend_box2,
ncol=self.legend_ncol,
fontsize=self.legend_fontsize)
plt.suptitle(self.title, fontsize=self.title_size)
plt.title(self.grp_title)
plt.gcf()
plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close()
logger.debug("{}.png".format(self.graph_image_name))
# TODO work though this for two axis
if self.enable_csv:
if self.data_set is not None and self.xaxis_categories is not None:
if len(self.xaxis_categories) == len(self.data_set[0]):
self.lf_csv.columns = []
self.lf_csv.rows = []
self.lf_csv.columns.append(self.xaxis_name)
self.lf_csv.columns.extend(self.label)
self.lf_csv.rows.append(self.xaxis_categories)
self.lf_csv.rows.extend(self.data_set)
self.lf_csv.filename = f"{self.graph_image_name}.csv"
self.lf_csv.generate_csv()
else:
raise ValueError(
"Length and x-axis values and y-axis values should be same.")
else:
logger.debug("No Dataset Found")
logger.debug("{}.csv".format(self.graph_image_name))
return "%s.png" % self.graph_image_name
class lf_stacked_graph: class lf_stacked_graph:
"""
usage: This will generate a vertically stacked graph with list _data_set as well as with dictionary _data_set.
example :
For a graph with dictionary data_set
obj = lf_stacked_graph(_data_set={'FCC0':0, 'FCC1':88.4,'FCC2':77.8,'FCC3':57.8,'FCC4':90.0,'FCC95':60.4,'FCC6':33.0},
_xaxis_name="", _yaxis_name="", _enable_csv=False, _remove_border=True)
obj.build_stacked_graph()
For a graph with list data_set
obj = lf_stacked_graph(_data_set=[['FCC0', 'FCC1', 'FCC2', 'FCC3', 'FCC4', 'FCC95', 'FCC6'],
[0, 88.4, 77.8, 57.8, 90.0, 60.4, 33.0],
[100.0, 11.6, 22.2, 42.2, 10.0, 39.6, 67.0]])
obj.build_stacked_graph()
"""
def __init__(self, def __init__(self,
_data_set=None, _data_set=None,
_xaxis_name="Stations", _xaxis_name="Stations",
@@ -273,7 +734,17 @@ class lf_stacked_graph:
_graph_image_name="image_name2", _graph_image_name="image_name2",
_color=None, _color=None,
_figsize=(9, 4), _figsize=(9, 4),
_enable_csv=True): _enable_csv=True,
_width=0.79,
_bar_text_color='white',
_bar_font_weight='bold',
_bar_font_size=8,
_legend_title="Issues",
_legend_bbox=(1.13, 1.01),
_legend_loc="upper right",
_remove_border=False,
_bar_text_rotation=0,
_x_ticklabels_rotation=0):
if _data_set is None: if _data_set is None:
_data_set = [[1, 2, 3, 4], [1, 1, 1, 1], [1, 1, 1, 1]] _data_set = [[1, 2, 3, 4], [1, 1, 1, 1], [1, 1, 1, 1]]
if _label is None: if _label is None:
@@ -287,9 +758,19 @@ class lf_stacked_graph:
self.color = _color self.color = _color
self.enable_csv = _enable_csv self.enable_csv = _enable_csv
self.lf_csv = lf_csv() self.lf_csv = lf_csv()
self.width = _width
self.bar_text_color = _bar_text_color
self.bar_font_weight = _bar_font_weight
self.bar_font_size = _bar_font_size
self.legend_title = _legend_title
self.legend_bbox = _legend_bbox
self.legend_loc = _legend_loc
self.remove_border = _remove_border
self.bar_text_rotation = _bar_text_rotation
self.x_ticklabels_rotation = _x_ticklabels_rotation
def build_stacked_graph(self): def build_stacked_graph(self):
plt.subplots(figsize=self.figsize) fig, axes_subplot = plt.subplots(figsize=self.figsize)
if self.color is None: if self.color is None:
self.color = [ self.color = [
"darkred", "darkred",
@@ -298,22 +779,65 @@ class lf_stacked_graph:
"skyblue", "skyblue",
"indigo", "indigo",
"plum"] "plum"]
plt.bar(self.data_set[0], self.data_set[1], color=self.color[0]) if type(self.data_set) is list:
plt.bar( plt.bar(self.data_set[0], self.data_set[1], color=self.color[0])
self.data_set[0], plt.bar(
self.data_set[2], self.data_set[0],
bottom=self.data_set[1], self.data_set[2],
color=self.color[1]) bottom=self.data_set[1],
if len(self.data_set) > 3: color=self.color[1])
for i in range(3, len(self.data_set)): if len(self.data_set) > 3:
plt.bar(self.data_set[0], self.data_set[i], for i in range(3, len(self.data_set)):
bottom=np.array(self.data_set[i - 2]) + np.array(self.data_set[i - 1]), color=self.color[i - 1]) plt.bar(self.data_set[0], self.data_set[i],
bottom=np.array(self.data_set[i - 2]) + np.array(self.data_set[i - 1]),color=self.color[i - 1])
plt.legend(self.label)
elif type(self.data_set) is dict:
lable_values = []
pass_values = []
fail_values = []
for i in self.data_set:
lable_values.append(i)
for j in self.data_set:
pass_values.append(self.data_set[j])
fail_values.append(round(float(100.0 - self.data_set[j]), 1))
width = self.width
figure_size, axes_subplot = plt.subplots(figsize=self.figsize)
# building vertical bar plot
bar_1 = plt.bar(lable_values, pass_values, width, color='green')
bar_2 = plt.bar(lable_values, fail_values, width, bottom=pass_values, color='red')
# inserting bar text
if len(list(self.data_set.keys())) > 10:
self.bar_text_rotation = 90
self.x_ticklabels_rotation = 90
for i, v in enumerate(pass_values):
if v != 0:
plt.text(i + .005, v * 0.45, "%s%s" % (v, "%"), color=self.bar_text_color,
fontweight=self.bar_font_weight,
fontsize=self.bar_font_size, ha="center", va="center", rotation=self.bar_text_rotation)
for i, v in enumerate(fail_values):
if v != 0:
plt.text(i + .005, v * 0.45 + pass_values[i], "%s%s" % (v, "%"), color=self.bar_text_color,
fontweight=self.bar_font_weight, fontsize=self.bar_font_size, ha="center", va="center" ,
rotation=self.bar_text_rotation)
plt.legend([bar_1, bar_2], self.label, title=self.legend_title, bbox_to_anchor=self.legend_bbox,
loc=self.legend_loc)
axes_subplot.set_xticks(list(self.data_set.keys()))
axes_subplot.set_xticklabels(list(self.data_set.keys()), rotation=self.x_ticklabels_rotation)
# to remove the borders
if self.remove_border:
for border in ['top', 'right', 'left', 'bottom']:
axes_subplot.spines[border].set_visible(False)
axes_subplot.yaxis.set_visible(False)
plt.xlabel(self.xaxis_name) plt.xlabel(self.xaxis_name)
plt.ylabel(self.yaxis_name) plt.ylabel(self.yaxis_name)
plt.legend(self.label) plt.savefig("%s.png" % self.graph_image_name, bbox_inches="tight", dpi=96)
plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close() plt.close()
print("{}.png".format(self.graph_image_name)) logger.debug("{}.png".format(self.graph_image_name))
if self.enable_csv: if self.enable_csv:
self.lf_csv.columns = self.label self.lf_csv.columns = self.label
self.lf_csv.rows = self.data_set self.lf_csv.rows = self.data_set
@@ -413,7 +937,7 @@ class lf_horizontal_stacked_graph:
labelbottom=False) # disable x-axis labelbottom=False) # disable x-axis
plt.savefig("%s.png" % self.graph_image_name, dpi=96) plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close() plt.close()
print("{}.png".format(self.graph_image_name)) logger.debug("{}.png".format(self.graph_image_name))
if self.enable_csv: if self.enable_csv:
self.lf_csv.columns = self.label self.lf_csv.columns = self.label
self.lf_csv.rows = self.data_set self.lf_csv.rows = self.data_set
@@ -430,7 +954,7 @@ class lf_line_graph:
_xaxis_label=None, _xaxis_label=None,
_graph_title="", _graph_title="",
_title_size=16, _title_size=16,
_graph_image_name="image_name", _graph_image_name="line_graph",
_label=None, _label=None,
_font_weight='bold', _font_weight='bold',
_color=None, _color=None,
@@ -445,9 +969,12 @@ class lf_line_graph:
_legend_fontsize=None, _legend_fontsize=None,
_marker=None, _marker=None,
_dpi=96, _dpi=96,
_enable_csv=False): _grid=True,
_enable_csv=False,
_reverse_x=False,
_reverse_y=False):
if _data_set is None: if _data_set is None:
_data_set = [[30.4, 55.3, 69.2, 37.1], [45.1, 67.2, 34.3, 22.4], [22.5, 45.6, 12.7, 34.8]] _data_set = [[30.4, 55.3, 69.2, 37.1, 44.0], [45.1, 67.2, 34.3, 22.4, 37.6], [22.5, 45.6, 12.7, 34.8, 22.5]]
if _xaxis_categories is None: if _xaxis_categories is None:
_xaxis_categories = [1, 2, 3, 4, 5] _xaxis_categories = [1, 2, 3, 4, 5]
if _xaxis_label is None: if _xaxis_label is None:
@@ -456,6 +983,9 @@ class lf_line_graph:
_label = ["bi-downlink", "bi-uplink", 'uplink'] _label = ["bi-downlink", "bi-uplink", 'uplink']
if _color is None: if _color is None:
_color = ['forestgreen', 'c', 'r', 'g', 'b', 'p'] _color = ['forestgreen', 'c', 'r', 'g', 'b', 'p']
if _marker is None:
_marker = ['s', 'o', 'v'] # available markers = '.', 'o', 'v', '<', 's', '*', 'p', 'P'
self.grid = _grid
self.data_set = _data_set self.data_set = _data_set
self.xaxis_name = _xaxis_name self.xaxis_name = _xaxis_name
self.yaxis_name = _yaxis_name self.yaxis_name = _yaxis_name
@@ -479,6 +1009,8 @@ class lf_line_graph:
self.legend_box = _legend_box self.legend_box = _legend_box
self.legend_ncol = _legend_ncol self.legend_ncol = _legend_ncol
self.legend_fontsize = _legend_fontsize self.legend_fontsize = _legend_fontsize
self.reverse_x = _reverse_x
self.reverse_y = _reverse_y
def build_line_graph(self): def build_line_graph(self):
plt.subplots(figsize=self.figsize) plt.subplots(figsize=self.figsize)
@@ -489,11 +1021,13 @@ class lf_line_graph:
data, data,
color=self.color[i], color=self.color[i],
label=self.label[i], label=self.label[i],
marker=self.marker) marker=self.marker[i])
i += 1 i += 1
plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15) plt.xlabel(self.xaxis_name, fontweight='bold', fontsize=15)
plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15) plt.ylabel(self.yaxis_name, fontweight='bold', fontsize=15)
if self.grid:
plt.grid(True, linestyle=':') # available line styles = ':', '-', '--', '-.'
plt.legend( plt.legend(
handles=self.legend_handles, handles=self.legend_handles,
loc=self.legend_loc, loc=self.legend_loc,
@@ -501,10 +1035,14 @@ class lf_line_graph:
ncol=self.legend_ncol, ncol=self.legend_ncol,
fontsize=self.legend_fontsize) fontsize=self.legend_fontsize)
plt.suptitle(self.grp_title, fontsize=self.title_size) plt.suptitle(self.grp_title, fontsize=self.title_size)
if self.reverse_y:
plt.gca().invert_yaxis()
if self.reverse_x:
plt.gca().invert_xaxis()
plt.gcf() plt.gcf()
plt.savefig("%s.png" % self.graph_image_name, dpi=96) plt.savefig("%s.png" % self.graph_image_name, dpi=96)
plt.close() plt.close()
print("{}.png".format(self.graph_image_name)) logger.debug("{}.png".format(self.graph_image_name))
if self.enable_csv: if self.enable_csv:
if self.data_set is not None: if self.data_set is not None:
self.lf_csv.columns = self.label self.lf_csv.columns = self.label
@@ -512,12 +1050,17 @@ class lf_line_graph:
self.lf_csv.filename = f"{self.graph_image_name}.csv" self.lf_csv.filename = f"{self.graph_image_name}.csv"
self.lf_csv.generate_csv() self.lf_csv.generate_csv()
else: else:
print("No Dataset Found") logger.debug("No Dataset Found")
print("{}.csv".format(self.graph_image_name)) logger.debug("{}.csv".format(self.graph_image_name))
return "%s.png" % self.graph_image_name return "%s.png" % self.graph_image_name
def main(): def main():
help_summary = '''\
This script facilitates the generation of comprehensive graphical reports. It offers a variety of graph types,
including bar graphs, horizontal bar graphs, scatter graphs, bar-line graphs, stacked graphs, horizontal stacked
graphs, and line graphs.
'''
# arguments # arguments
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog='lf_graph.py', prog='lf_graph.py',
@@ -551,10 +1094,44 @@ INCLUDE_IN_README
dest='lfmgr', dest='lfmgr',
help='sample argument: where LANforge GUI is running', help='sample argument: where LANforge GUI is running',
default='localhost') default='localhost')
# logging configuration
parser.add_argument(
'--debug',
help='--debug this will enable debugging in py-json method',
action='store_true')
parser.add_argument('--log_level',
default=None,
help='Set logging level: debug | info | warning | error | critical')
parser.add_argument(
"--lf_logger_config_json",
help="--lf_logger_config_json <json file> , json configuration of logger")
parser.add_argument('--help_summary', help='Show summary of what this script does', default=None,
action="store_true")
# the args parser is not really used , this is so the report is not generated when testing # the args parser is not really used , this is so the report is not generated when testing
# the imports with --help # the imports with --help
args = parser.parse_args() args = parser.parse_args()
print("LANforge manager {lfmgr}".format(lfmgr=args.lfmgr))
if args.help_summary:
print(help_summary)
exit(0)
# set up logger
logger_config = lf_logger_config.lf_logger_config()
# set the logger level to debug
if args.log_level:
logger_config.set_level(level=args.log_level)
# lf_logger_config_json will take presidence to changing debug levels
if args.lf_logger_config_json:
# logger_config.lf_logger_config_json = "lf_logger_config.json"
logger_config.lf_logger_config_json = args.lf_logger_config_json
logger_config.load_lf_logger_config()
logger.debug("LANforge manager {lfmgr}".format(lfmgr=args.lfmgr))
output_html_1 = "graph_1.html" output_html_1 = "graph_1.html"
output_pdf_1 = "graph_1.pdf" output_pdf_1 = "graph_1.pdf"
@@ -594,6 +1171,9 @@ INCLUDE_IN_README
_label=["bi-downlink", "bi-uplink", 'uplink'], _label=["bi-downlink", "bi-uplink", 'uplink'],
_color=None, _color=None,
_color_edge='red', _color_edge='red',
_show_bar_value=True,
_text_font=7,
_text_rotation=None,
_enable_csv=True) _enable_csv=True)
graph_html_obj = """ graph_html_obj = """
<img align='center' style='padding:15;margin:5;width:1000px;' src=""" + "%s" % (graph.build_bar_graph()) + """ border='1' /> <img align='center' style='padding:15;margin:5;width:1000px;' src=""" + "%s" % (graph.build_bar_graph()) + """ border='1' />
@@ -604,6 +1184,10 @@ INCLUDE_IN_README
test_file.write(graph_html_obj) test_file.write(graph_html_obj)
test_file.close() test_file.close()
graph = lf_line_graph()
graph.build_line_graph()
# write to pdf # write to pdf
# write logic to generate pdf here # write logic to generate pdf here
# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
@@ -612,6 +1196,49 @@ INCLUDE_IN_README
options = {"enable-local-file-access": None} options = {"enable-local-file-access": None}
pdfkit.from_file(output_html_2, output_pdf_2, options=options) pdfkit.from_file(output_html_2, output_pdf_2, options=options)
# test build_bar_graph_horizontal with defaults
dataset = [[45, 67, 34, 22, 31, 52, 60, 71, 24, 25, 45, 67, 34, 22, 31, 52, 60, 71, 24, 25], [22, 45, 12, 34, 70, 80, 14, 35, 44, 45,22, 45, 12, 34, 70, 80, 14, 35, 44, 45 ], [30, 55, 69, 37, 77, 24, 25, 77, 77, 80, 30, 55, 69, 37, 77, 24, 25, 77, 77, 80]]
y_axis_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
# calculate the height of the y-axis .25 * number of values
y_fig_size = len(y_axis_values) * len(dataset) * .35
x_fig_size = 10
output_html_3 = "graph_3.html"
output_pdf_3 = "graph_3.pdf"
graph = lf_bar_graph_horizontal(_data_set=dataset,
_xaxis_name="Throughput 2 (Mbps)",
_yaxis_name="stations",
_yaxis_categories=y_axis_values,
_graph_image_name="Bi-single_radio_2.4GHz",
_label=["bi-downlink", "bi-uplink", 'uplink'],
_color=None,
_color_edge='red',
_figsize=(x_fig_size, y_fig_size),
_show_bar_value= True,
_text_font=6,
_text_rotation=True,
_enable_csv=True)
graph_html_obj = """
<img align='center' style='padding:15;margin:5;width:1000px;' src=""" + "%s" % (graph.build_bar_graph_horizontal()) + """ border='1' />
<br><br>
"""
#
test_file = open(output_html_3, "w")
test_file.write(graph_html_obj)
test_file.close()
# write to pdf
# write logic to generate pdf here
# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
# sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
# prevent eerror Blocked access to file
options = {"enable-local-file-access": None}
pdfkit.from_file(output_html_3, output_pdf_3, options=options)
# Unit Test # Unit Test
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -86,7 +86,10 @@ class lf_report:
self.graph_title = _graph_title self.graph_title = _graph_title
self.date = _date self.date = _date
self.output_html = _output_html self.output_html = _output_html
if _output_html.lower().endswith(".pdf"):
raise ValueError("HTML output file cannot end with suffix '.pdf'")
self.path_date_time = _path_date_time self.path_date_time = _path_date_time
self.report_location = "" # used by lf_check.py to know where to write the meta data "Report Location:::/home/lanforge/html-reports/wifi-capacity-2021-08-17-04-02-56"
self.write_output_html = "" self.write_output_html = ""
self.write_output_index_html = "" self.write_output_index_html = ""
self.output_pdf = _output_pdf self.output_pdf = _output_pdf
@@ -101,6 +104,8 @@ class lf_report:
self.pdf_link_html = "" self.pdf_link_html = ""
self.objective = _obj self.objective = _obj
self.obj_title = _obj_title self.obj_title = _obj_title
self.description = ""
self.desc_title = ""
# self.systeminfopath = "" # self.systeminfopath = ""
self.date_time_directory = "" self.date_time_directory = ""
self.log_directory = "" self.log_directory = ""
@@ -132,9 +137,12 @@ class lf_report:
shutil.copy(banner_src_file, banner_dst_file) shutil.copy(banner_src_file, banner_dst_file)
def move_data(self, directory=None, _file_name=None, directory_name=None): def move_data(self, directory=None, _file_name=None, directory_name=None):
if directory_name is None: if directory_name is None:
_src_file = str(self.current_path) + '/' + str(_file_name) _src_file = str(self.current_path) + '/' + str(_file_name)
_dst_file = str(self.path_date_time) + '/' + str(directory) + '/' + str(_file_name) if directory is None:
_dst_file = str(self.path_date_time)
else:
_dst_file = str(self.path_date_time) + '/' + str(directory) + '/' + str(_file_name)
else: else:
_src_file = str(self.current_path) + '/' + str(directory_name) _src_file = str(self.current_path) + '/' + str(directory_name)
_dst_file = str(self.path_date_time) + '/' + str(directory_name) _dst_file = str(self.path_date_time) + '/' + str(directory_name)
@@ -251,6 +259,10 @@ class lf_report:
fname, ext = os.path.splitext(_graph_title) fname, ext = os.path.splitext(_graph_title)
self.csv_file_name = fname + ".csv" self.csv_file_name = fname + ".csv"
def write_dataframe_to_csv(self, _index=False):
csv_file = "{path_date_time}/{csv_file_name}".format(path_date_time=self.path_date_time,csv_file_name=self.csv_file_name)
self.dataframe.to_csv(csv_file,index=_index)
# The _date is set when class is enstanciated / created so this set_date should be used with caution, used to synchronize results # The _date is set when class is enstanciated / created so this set_date should be used with caution, used to synchronize results
def set_date(self, _date): def set_date(self, _date):
self.date = _date self.date = _date
@@ -275,6 +287,10 @@ class lf_report:
self.objective = _obj self.objective = _obj
self.obj_title = _obj_title self.obj_title = _obj_title
def set_desc_html(self, _desc_title, _desc):
self.description = _desc
self.desc_title = _desc_title
def set_graph_image(self, _graph_image): def set_graph_image(self, _graph_image):
self.graph_image = _graph_image self.graph_image = _graph_image
@@ -305,8 +321,20 @@ class lf_report:
output_file = str(self.path_date_time) + '/' + str(file) output_file = str(self.path_date_time) + '/' + str(file)
logger.info("output file {}".format(output_file)) logger.info("output file {}".format(output_file))
return output_file return output_file
# Report Location:::/<locaton> as a key in lf_check.py
def write_report_location(self):
self.report_location = self.path_date_time
logger.info("Report Location:::{report_location}".format(report_location=self.report_location))
def write_html(self): def write_html(self):
if not self.output_html:
logger.info("no html file name, skipping report generation")
return
if self.output_html.lower().endswith(".pdf"):
raise ValueError("write_html: HTML filename [%s] should not end with .pdf" % self.output_html)
if self.write_output_html.endswith(".pdf"):
raise ValueError("wrong suffix for an HTML file: %s" % self.write_output_html)
self.write_output_html = str(self.path_date_time) + '/' + str(self.output_html) self.write_output_html = str(self.path_date_time) + '/' + str(self.output_html)
logger.info("write_output_html: {}".format(self.write_output_html)) logger.info("write_output_html: {}".format(self.write_output_html))
try: try:
@@ -319,7 +347,13 @@ class lf_report:
return self.write_output_html return self.write_output_html
def write_index_html(self): def write_index_html(self):
self.write_output_index_html = str(self.path_date_time) + '/' + str("index.html") # LAN-1535 scripting: test_l3.py output masks other output when browsing.
# consider renaming index.html to readme.html
# self.write_output_index_html = str(self.path_date_time) + '/' + str("index.html")
if not self.output_html:
logger.info("no html file name, skipping report generation")
return
self.write_output_index_html = str(self.path_date_time) + '/' + str("readme.html")
logger.info("write_output_index_html: {}".format(self.write_output_index_html)) logger.info("write_output_index_html: {}".format(self.write_output_index_html))
try: try:
test_file = open(self.write_output_index_html, "w") test_file = open(self.write_output_index_html, "w")
@@ -331,6 +365,11 @@ class lf_report:
return self.write_output_index_html return self.write_output_index_html
def write_html_with_timestamp(self): def write_html_with_timestamp(self):
if not self.output_html:
logger.info("no html file name, skipping report generation")
return
if self.output_html.lower().endswith(".pdf"):
raise ValueError("write_html_with_timestamp: will not save file with PDF suffix [%s]" % self.output_html)
self.write_output_html = "{}/{}-{}".format(self.path_date_time, self.date, self.output_html) self.write_output_html = "{}/{}-{}".format(self.path_date_time, self.date, self.output_html)
logger.info("write_output_html: {}".format(self.write_output_html)) logger.info("write_output_html: {}".format(self.write_output_html))
try: try:
@@ -349,7 +388,9 @@ class lf_report:
# write logic to generate pdf here # write logic to generate pdf here
# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
# sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb # sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
if not self.output_pdf:
logger.info("write_pdf: no pdf file name, skipping pdf output")
return
options = {"enable-local-file-access": None, options = {"enable-local-file-access": None,
'orientation': _orientation, 'orientation': _orientation,
'page-size': _page_size} # prevent error Blocked access to file 'page-size': _page_size} # prevent error Blocked access to file
@@ -363,7 +404,9 @@ class lf_report:
# write logic to generate pdf here # write logic to generate pdf here
# wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb # wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
# sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb # sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
if not self.output_pdf:
logger.info("write_pdf_with_timestamp: no pdf file name, skipping pdf output")
return
options = {"enable-local-file-access": None, options = {"enable-local-file-access": None,
'orientation': _orientation, 'orientation': _orientation,
'page-size': _page_size} # prevent error Blocked access to file 'page-size': _page_size} # prevent error Blocked access to file
@@ -374,6 +417,14 @@ class lf_report:
pdf_link_path = "{}/{}-{}".format(self.path_date_time, self.date, self.output_pdf) pdf_link_path = "{}/{}-{}".format(self.path_date_time, self.date, self.output_pdf)
return pdf_link_path return pdf_link_path
# used for relative pathing
def get_pdf_file(self):
if not self.output_pdf:
logger.info("get_pdf_file: no pdf name, returning None")
return None
pdf_file = "{}-{}".format(self.date, self.output_pdf)
return pdf_file
def build_pdf_link(self, _pdf_link_name, _pdf_link_path): def build_pdf_link(self, _pdf_link_name, _pdf_link_path):
self.pdf_link_html = """ self.pdf_link_html = """
<!-- pdf link --> <!-- pdf link -->
@@ -392,7 +443,8 @@ class lf_report:
def generate_report(self): def generate_report(self):
self.write_html() self.write_html()
self.write_pdf() if self.output_pdf:
self.write_pdf()
def build_all(self): def build_all(self):
self.build_banner() self.build_banner()
@@ -462,6 +514,32 @@ class lf_report:
) )
self.html += self.banner_html self.html += self.banner_html
def build_banner_left_h2_font(self):
# NOTE: {{ }} are the ESCAPED curly braces
# JBR suggests rename method to start_html_doc()
# This method violates DRY, if the ID of the body/div#BannerBack/div element is actually necessary
# to specify, this needs to be made a parameter for build_banner() or start_html_doc()
self.banner_html = """<!DOCTYPE html>
<html lang='en'>
{head_tag}
<body>
<div id='BannerBack'>
<div id='BannerLeft'>
<img id='BannerLogo' align='right' src="CandelaLogo2-90dpi-200x90-trans.png" border='0'/>
<div class='HeaderStyle'>
<h2 class='TitleFontPrint' style='color:darkgreen;'>{title}</h2>
<h4 class='TitleFontPrintSub' style='color:darkgreen;'>{date}</h4>
</div>
</div>
</div>
""".format(
head_tag=self.get_html_head(title=self.title),
title=self.title,
date=self.date,
)
self.html += self.banner_html
def build_table_title(self): def build_table_title(self):
self.table_title_html = """ self.table_title_html = """
<!-- Table Title--> <!-- Table Title-->
@@ -483,6 +561,14 @@ class lf_report:
</div>""".format(text=self.text) </div>""".format(text=self.text)
self.html += self.text_html self.html += self.text_html
def build_text_simple(self):
# please do not use 'style=' tags unless you cannot override a class
self.text_html = """
<p align='left' width='900'>{text}</p>
""".format(text=self.text)
self.html += self.text_html
def build_date_time(self): def build_date_time(self):
self.date_time = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%m-m-%S-s")).replace(':', '-') self.date_time = str(datetime.datetime.now().strftime("%Y-%m-%d-%H-h-%m-m-%S-s")).replace(':', '-')
return self.date_time return self.date_time
@@ -530,10 +616,10 @@ class lf_report:
plt.savefig(str(self.path_date_time) + '/pie-chart.png') plt.savefig(str(self.path_date_time) + '/pie-chart.png')
def save_bar_chart(self, xlabel, ylabel, bar_chart_data, name): def save_bar_chart(self, xlabel, ylabel, bar_chart_data, name):
plot = bar_chart_data.plot.bar(alpha=0.9, rot=0, width=0.9, linewidth=0.9) plot = bar_chart_data.plot.bar(alpha=0.9, rot=0, width=0.9, linewidth=0.9, figsize=(10, 6))
plot.legend(bbox_to_anchor=(1.0, 1.0)) plot.legend(bbox_to_anchor=(1.0, 1.0))
plot.spines['right'].set_visible(False) # plot.spines['right'].set_visible(False)
plot.spines['top'].set_visible(False) # plot.spines['top'].set_visible(False)
# plot.set_title(name) # plot.set_title(name)
for p in plot.patches: for p in plot.patches:
height = p.get_height() height = p.get_height()
@@ -545,7 +631,7 @@ class lf_report:
annotation_clip=False, annotation_clip=False,
ha='center', va='bottom') ha='center', va='bottom')
# plt.xlabel(xlabel) # plt.xlabel(xlabel)
plt.xticks(rotation=45, horizontalalignment='right', fontweight='light', fontsize='small', ) plt.xticks(rotation=0, horizontalalignment='right', fontweight='light', fontsize='small', )
plt.ylabel(ylabel) plt.ylabel(ylabel)
plt.tight_layout() plt.tight_layout()
plt.savefig(str(self.path_date_time) + '/' + name + '.png') plt.savefig(str(self.path_date_time) + '/' + name + '.png')
@@ -647,6 +733,16 @@ function copyTextToClipboard(ele) {
objective=self.objective) objective=self.objective)
self.html += self.obj_html self.html += self.obj_html
def build_description(self):
self.obj_html = """
<!-- Test Description -->
<h3 align='left'>{title}</h3>
<p align='left' width='900'>{description}</p>
""".format(title=self.desc_title,
description=self.description)
self.html += self.obj_html
def build_graph_title(self): def build_graph_title(self):
self.table_graph_html = """ self.table_graph_html = """
<div class='HeaderStyle'> <div class='HeaderStyle'>
@@ -660,6 +756,12 @@ function copyTextToClipboard(ele) {
""".format(image=self.graph_image) """.format(image=self.graph_image)
self.html += self.graph_html_obj self.html += self.graph_html_obj
def build_graph_without_border(self):
self.graph_html_obj = """
<img align='left' style='padding:15px;margin:5px 5px 2em 5px;width:1000px;' src='{image}' border='0' />
""".format(image=self.graph_image)
self.html += self.graph_html_obj
def end_content_div(self): def end_content_div(self):
self.html += "\n</div><!-- end contentDiv -->\n" self.html += "\n</div><!-- end contentDiv -->\n"
@@ -676,9 +778,45 @@ function copyTextToClipboard(ele) {
""".format(image=name) """.format(image=name)
self.html += self.chart_html_obj self.html += self.chart_html_obj
def build_chart_custom(self, name, align='center',padding='15px',margin='5px 5px 2em 5px',width='500px',height='500px'):
self.chart_html_obj = """
<img align='{align}' style='padding:{padding};margin:{margin};width:{width};height:{height};'
src='{image}'/> """.format(image=name,align=align,padding=padding,margin=margin,width=width, height=height)
self.html += self.chart_html_obj
def build_banner_cover(self):
# NOTE: {{ }} are the ESCAPED curly braces
# JBR suggests rename method to start_html_doc()
# This method violates DRY, if the ID of the body/div#BannerBack/div element is actually necessary
# to specify, this needs to be made a parameter for build_banner() or start_html_doc()
self.banner_html = """<!DOCTYPE html>
<html lang='en'>
{head_tag}
<body>
<div id='BannerBack' style='height: 100%; max-height: 100%;'>
<div id='BannerLeft' style="margin: 0%; background-size: 100%; max-height: 100%; max-width: 100%; width: 100%; height: 100%;">
<img id='BannerLogo' align='right' src="CandelaLogo2-90dpi-200x90-trans.png" border='0'/>
<div class='HeaderStyle'>
<h1 class='TitleFontPrint' style='color:darkgreen;'>{title}</h1>
<h4 class='TitleFontPrintSub' style='color:darkgreen;'>{date}</h4>
</div>
</div>
</div>
""".format(
head_tag=self.get_html_head(title=self.title),
title=self.title,
date=self.date,
)
self.html += self.banner_html
# Unit Test # Unit Test
if __name__ == "__main__": if __name__ == "__main__":
help_summary = '''\
This script is designed to generate reports in file formats such as PDF and HTML, accommodating various user
preferences. The reports can encompass a range of elements, including graphs, tables, and customizable objectives,
tailored to meet specific user requirements
'''
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="lf_report.py", prog="lf_report.py",
formatter_class=argparse.RawTextHelpFormatter, formatter_class=argparse.RawTextHelpFormatter,
@@ -686,7 +824,15 @@ if __name__ == "__main__":
parser.add_argument('--lfmgr', help='sample argument: where LANforge GUI is running', default='localhost') parser.add_argument('--lfmgr', help='sample argument: where LANforge GUI is running', default='localhost')
# the args parser is not really used , this is so the report is not generated when testing # the args parser is not really used , this is so the report is not generated when testing
# the imports with --help # the imports with --help
parser.add_argument('--help_summary', help='Show summary of what this script does', default=None,
action="store_true")
args = parser.parse_args() args = parser.parse_args()
# help summary
if args.help_summary:
print(help_summary)
exit(0)
logger.info("LANforge manager {lfmgr}".format(lfmgr=args.lfmgr)) logger.info("LANforge manager {lfmgr}".format(lfmgr=args.lfmgr))
# Testing: generate data frame # Testing: generate data frame
@@ -723,6 +869,12 @@ if __name__ == "__main__":
report.set_table_dataframe(dataframe2) report.set_table_dataframe(dataframe2)
report.build_table() report.build_table()
report.build_chart_title('default width')
report.build_chart("banner.png")
report.build_chart_title('custom width')
report.build_chart_custom(name="banner.png",width="1000")
# report.build_all() # report.build_all()
# report.build_footer() # report.build_footer()
report.build_footer_no_png() report.build_footer_no_png()