SuperModelingFactory 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ExcelMaster/ExcelFormatTool.py +487 -0
- ExcelMaster/ExcelMaster.py +917 -0
- ExcelMaster/Template.py +525 -0
- ExcelMaster/Utility.py +233 -0
- ExcelMaster/__init__.py +3 -0
- Modeling_Tool/Core/Binning_Tool.py +1608 -0
- Modeling_Tool/Core/Binning_Tool.pyi +48 -0
- Modeling_Tool/Core/Check_DuckDB_Compatibility.py +835 -0
- Modeling_Tool/Core/Json_Data_Converter.py +621 -0
- Modeling_Tool/Core/Model_Registry_Tool.py +199 -0
- Modeling_Tool/Core/ODPS_Tool.py +284 -0
- Modeling_Tool/Core/Slope_Tool.py +356 -0
- Modeling_Tool/Core/Slope_Tool.pyi +31 -0
- Modeling_Tool/Core/XOR_Encryptor.py +207 -0
- Modeling_Tool/Core/XOR_Encryptor.pyi +26 -0
- Modeling_Tool/Core/__init__.py +99 -0
- Modeling_Tool/Core/kDataFrame.py +228 -0
- Modeling_Tool/Core/kDataFrame.pyi +43 -0
- Modeling_Tool/Core/sample_weight_utils.py +77 -0
- Modeling_Tool/Core/utils.py +2672 -0
- Modeling_Tool/Eval/Evaluation_Tool.py +1452 -0
- Modeling_Tool/Eval/Evaluation_Tool.pyi +47 -0
- Modeling_Tool/Eval/Model_Eval_Tool.py +2548 -0
- Modeling_Tool/Eval/Model_Eval_Tool.pyi +37 -0
- Modeling_Tool/Eval/__init__.py +54 -0
- Modeling_Tool/Eval/evaluate_model.py +2008 -0
- Modeling_Tool/Eval/evaluate_model.pyi +50 -0
- Modeling_Tool/Eval/weighted_eval_utils.py +326 -0
- Modeling_Tool/Explainability/Coalition_Structure.py +305 -0
- Modeling_Tool/Explainability/Model_Explainer.py +743 -0
- Modeling_Tool/Explainability/__init__.py +24 -0
- Modeling_Tool/Feature/Distribution_Tool.py +509 -0
- Modeling_Tool/Feature/Distribution_Tool.pyi +42 -0
- Modeling_Tool/Feature/Feature_Insights.py +762 -0
- Modeling_Tool/Feature/Feature_Insights.pyi +33 -0
- Modeling_Tool/Feature/PSI_Tool.py +1195 -0
- Modeling_Tool/Feature/PSI_Tool.pyi +29 -0
- Modeling_Tool/Feature/WOE_Engine_Feature_Patch.py +355 -0
- Modeling_Tool/Feature/__init__.py +40 -0
- Modeling_Tool/Model/Backward_Tool.py +778 -0
- Modeling_Tool/Model/Backward_Tool.pyi +45 -0
- Modeling_Tool/Model/GBM_Search_Tool.py +251 -0
- Modeling_Tool/Model/GBM_Tool.py +1610 -0
- Modeling_Tool/Model/GBM_Tool.pyi +90 -0
- Modeling_Tool/Model/LRM_Tool.py +1198 -0
- Modeling_Tool/Model/LRM_Tool.pyi +47 -0
- Modeling_Tool/Model/__init__.py +61 -0
- Modeling_Tool/Sample/Distribution_Adaptation.py +131 -0
- Modeling_Tool/Sample/Distribution_Adaptation.pyi +30 -0
- Modeling_Tool/Sample/Reject_Infer.py +413 -0
- Modeling_Tool/Sample/Reject_Infer.pyi +43 -0
- Modeling_Tool/Sample/Sample_Split.py +520 -0
- Modeling_Tool/Sample/Sample_Split.pyi +43 -0
- Modeling_Tool/Sample/__init__.py +31 -0
- Modeling_Tool/UAT/UAT_Consistency_Checker.py +1180 -0
- Modeling_Tool/UAT/__init__.py +19 -0
- Modeling_Tool/WOE/WOE_Adapter.py +204 -0
- Modeling_Tool/WOE/WOE_Adapter.pyi +21 -0
- Modeling_Tool/WOE/WOE_Master.py +491 -0
- Modeling_Tool/WOE/WOE_Master.pyi +40 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.py +3324 -0
- Modeling_Tool/WOE/WOE_Monotone_Binner.pyi +71 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.py +919 -0
- Modeling_Tool/WOE/WOE_Plot_Tool.pyi +46 -0
- Modeling_Tool/WOE/WOE_Report_Builder.py +214 -0
- Modeling_Tool/WOE/WOE_Report_Builder.pyi +24 -0
- Modeling_Tool/WOE/WOE_Tool.py +1094 -0
- Modeling_Tool/WOE/WOE_Tool.pyi +41 -0
- Modeling_Tool/WOE/__init__.py +86 -0
- Modeling_Tool/WOE/plot_woe_tool.py +290 -0
- Modeling_Tool/WOE/plot_woe_tool.pyi +25 -0
- Modeling_Tool/__init__.py +176 -0
- Report/Report_Tool.py +380 -0
- Report/__init__.py +3 -0
- supermodelingfactory-0.2.0.dist-info/METADATA +268 -0
- supermodelingfactory-0.2.0.dist-info/RECORD +79 -0
- supermodelingfactory-0.2.0.dist-info/WHEEL +5 -0
- supermodelingfactory-0.2.0.dist-info/licenses/LICENSE +102 -0
- supermodelingfactory-0.2.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
__Author__ = "Jingkai SUN"
|
|
2
|
+
__Date__ = "2025.05.15"
|
|
3
|
+
|
|
4
|
+
import xlsxwriter
|
|
5
|
+
from xlsxwriter.utility import xl_rowcol_to_cell, xl_range, xl_cell_to_rowcol
|
|
6
|
+
import openpyxl
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
import logging
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
from .ExcelFormatTool import ExcelFormat
|
|
14
|
+
from .Utility import *
|
|
15
|
+
|
|
16
|
+
class ExcelWorkbook(ExcelFormat):
|
|
17
|
+
""" Write anything to Excel (Workbook-level Operator). """
|
|
18
|
+
def __init__(self, filepath, verbose = True):
|
|
19
|
+
super().__init__(filepath)
|
|
20
|
+
self.verbose = verbose
|
|
21
|
+
self.ws_dict = {}
|
|
22
|
+
|
|
23
|
+
def to_cell_range_text(self, first_row, first_col, last_row, last_col):
|
|
24
|
+
""" To Excel Cell Range. """
|
|
25
|
+
return xl_range(first_row, first_col, last_row, last_col)
|
|
26
|
+
|
|
27
|
+
def cell_range_to_loc(self, cell_range_text):
|
|
28
|
+
""" Convert text cell range expression to a list of values. """
|
|
29
|
+
cell_range_list = cell_range_text.split(":")
|
|
30
|
+
res = []
|
|
31
|
+
for x in cell_range_list:
|
|
32
|
+
res += list(xl_cell_to_rowcol(x))
|
|
33
|
+
return res
|
|
34
|
+
|
|
35
|
+
def colletter_to_textloc(self, row_index, col_letter):
|
|
36
|
+
""" Append Row Index to the given Column Letter-Formatted Index. """
|
|
37
|
+
col_letter = col_letter if ":" in col_letter else (col_letter + ":" + col_letter)
|
|
38
|
+
output = col_letter.split(":")
|
|
39
|
+
output = ":".join([col + str(row_index) for col in output])
|
|
40
|
+
return output
|
|
41
|
+
|
|
42
|
+
def set_color_scale(self, worksheet, cell_range,
|
|
43
|
+
colors = ("#F8696B", "#FFEB84", "#63BE7B")):
|
|
44
|
+
""" Set Color Scale. """
|
|
45
|
+
if len(colors) == 3:
|
|
46
|
+
f = {
|
|
47
|
+
"type": "3_color_scale",
|
|
48
|
+
"min_color": colors[0],
|
|
49
|
+
"mid_color": colors[1],
|
|
50
|
+
"max_color": colors[2],
|
|
51
|
+
}
|
|
52
|
+
elif len(colors) == 2:
|
|
53
|
+
f = {
|
|
54
|
+
"type": "2_color_scale",
|
|
55
|
+
"min_color": colors[0],
|
|
56
|
+
"max_color": colors[1],
|
|
57
|
+
}
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError("Please give 2 or 3 colors!")
|
|
60
|
+
|
|
61
|
+
if isinstance(cell_range, list) or isinstance(cell_range, tuple):
|
|
62
|
+
cell_range = self.to_cell_range_text(*cell_range)
|
|
63
|
+
|
|
64
|
+
worksheet.conditional_format(cell_range, f)
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
def set_data_bar(self, worksheet, cell_range, bar_color = "#63C384"):
|
|
68
|
+
""" Set Data Bar in a Worksheet for a range of cells. """
|
|
69
|
+
|
|
70
|
+
f = {'type': 'data_bar',
|
|
71
|
+
'data_bar_2010': True,
|
|
72
|
+
"bar_color": bar_color}
|
|
73
|
+
|
|
74
|
+
if isinstance(cell_range, list) or isinstance(cell_range, tuple):
|
|
75
|
+
cell_range = self.to_cell_range_text(*cell_range)
|
|
76
|
+
|
|
77
|
+
worksheet.conditional_format(cell_range, f)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
def set_cell_format(self, worksheet, cell_range, cformat, cell_condition = None):
|
|
81
|
+
""" Set format for a range of cell. """
|
|
82
|
+
|
|
83
|
+
if cell_condition and isinstance(cell_condition, tuple):
|
|
84
|
+
criteria = cell_condition[0].lower()
|
|
85
|
+
value = cell_condition[1]
|
|
86
|
+
|
|
87
|
+
if isinstance(cell_range, list) or isinstance(cell_range, tuple):
|
|
88
|
+
cell_range = self.to_cell_range_text(*cell_range)
|
|
89
|
+
|
|
90
|
+
if isinstance(cformat, str):
|
|
91
|
+
cformat = self.dict_cell_format[cformat]
|
|
92
|
+
|
|
93
|
+
if cell_condition and isinstance(cell_condition, tuple):
|
|
94
|
+
|
|
95
|
+
if (criteria == 'between') or (criteria == 'not between'):
|
|
96
|
+
worksheet.conditional_format(cell_range, {"type": "cell",
|
|
97
|
+
"criteria": criteria,
|
|
98
|
+
"minimum": value[0],
|
|
99
|
+
"maximum": value[1],
|
|
100
|
+
"format": cformat})
|
|
101
|
+
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
worksheet.conditional_format(cell_range, {"type": "cell",
|
|
105
|
+
"criteria": criteria,
|
|
106
|
+
"value": value,
|
|
107
|
+
"format": cformat})
|
|
108
|
+
return 0
|
|
109
|
+
|
|
110
|
+
worksheet.conditional_format(cell_range, {'type': 'no_errors', "format": cformat})
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
def set_cell_format_rbyr(self, worksheet, start_row, condition_list, condition, ifelse_col, cformat = "YELLOW_BG"):
|
|
114
|
+
""" Conditionally Highlight Cells Row by Row. """
|
|
115
|
+
|
|
116
|
+
if_col = ifelse_col[0]
|
|
117
|
+
else_col = ifelse_col[1]
|
|
118
|
+
|
|
119
|
+
i = 0
|
|
120
|
+
row_concur = start_row
|
|
121
|
+
while i < len(condition_list):
|
|
122
|
+
target = condition_list[i]
|
|
123
|
+
|
|
124
|
+
if val_input_condition(target, condition):
|
|
125
|
+
if if_col:
|
|
126
|
+
self.set_cell_format(worksheet,
|
|
127
|
+
cell_range=self.colletter_to_textloc(row_concur + 1, if_col),
|
|
128
|
+
cformat=cformat)
|
|
129
|
+
|
|
130
|
+
else:
|
|
131
|
+
if else_col:
|
|
132
|
+
self.set_cell_format(worksheet,
|
|
133
|
+
cell_range=self.colletter_to_textloc(row_concur + 1, else_col),
|
|
134
|
+
cformat=cformat)
|
|
135
|
+
|
|
136
|
+
i += 1
|
|
137
|
+
row_concur = row_concur + 1
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
def remove_tmp_img(self, img_pattern = r".tmp_image_[0-9]+.png"):
|
|
141
|
+
""" Remove Temp Images. """
|
|
142
|
+
tmp_imgs = list_files(location = "./", pattern = img_pattern)
|
|
143
|
+
if len(tmp_imgs) > 0:
|
|
144
|
+
for f in tmp_imgs:
|
|
145
|
+
os.remove(f)
|
|
146
|
+
return 0
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
def close_workbook(self):
|
|
150
|
+
'''
|
|
151
|
+
Finish writing the contents of the workbook and close the file.
|
|
152
|
+
'''
|
|
153
|
+
self.workbook.close()
|
|
154
|
+
self.remove_tmp_img(img_pattern = r".tmp_image_[0-9]+.png")
|
|
155
|
+
logging.info("All temp images have been removed.")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class ExcelMaster(ExcelWorkbook):
|
|
159
|
+
""" Write anything to Excel (Worksheet-level Operator) """
|
|
160
|
+
def __init__(self, filepath, verbose, gap_number = 2, init_loc = (0, 0)):
|
|
161
|
+
super().__init__(filepath, verbose)
|
|
162
|
+
self.curr_row = init_loc[0]
|
|
163
|
+
self.curr_col = init_loc[1]
|
|
164
|
+
self.gap_number = gap_number + 1
|
|
165
|
+
|
|
166
|
+
self.default_row_height = 20
|
|
167
|
+
self.default_col_width = 64
|
|
168
|
+
|
|
169
|
+
self.max_nrows = 1048576
|
|
170
|
+
self.max_ncols = 16384
|
|
171
|
+
|
|
172
|
+
def add_worksheet(self, name, hide_grid = True, reset_loc = True, cell_scale = True, auto_fit = False, zoom_perc = 100, tab_color = None):
|
|
173
|
+
""" Add a worksheet. """
|
|
174
|
+
ws = self.workbook.add_worksheet(name)
|
|
175
|
+
|
|
176
|
+
if hide_grid:
|
|
177
|
+
ws.hide_gridlines(2)
|
|
178
|
+
|
|
179
|
+
if reset_loc:
|
|
180
|
+
self.reset_curr_loc()
|
|
181
|
+
|
|
182
|
+
if isinstance(cell_scale, tuple):
|
|
183
|
+
self.set_cell_size(ws, cell_scale)
|
|
184
|
+
if cell_scale is True:
|
|
185
|
+
self.set_cell_size(ws)
|
|
186
|
+
|
|
187
|
+
if auto_fit:
|
|
188
|
+
ws.auto_fit()
|
|
189
|
+
|
|
190
|
+
if tab_color:
|
|
191
|
+
ws.set_tab_color(tab_color)
|
|
192
|
+
|
|
193
|
+
ws.set_zoom(zoom_perc)
|
|
194
|
+
self.ws_dict[name] = ws
|
|
195
|
+
self.engine.sheets[name] = ws
|
|
196
|
+
return ws
|
|
197
|
+
|
|
198
|
+
def reset_curr_loc(self, loc = (0, 0)):
|
|
199
|
+
""" Reset Current Location. """
|
|
200
|
+
|
|
201
|
+
self.curr_row = loc[0]
|
|
202
|
+
self.curr_col = loc[1]
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
def _reset_cell_size(self):
|
|
206
|
+
""" Reset Cell Size to Default Value. """
|
|
207
|
+
self.default_row_height = 20
|
|
208
|
+
self.default_col_width = 64
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
def set_cell_size(self, worksheet, size_scale = (1, 1)):
|
|
212
|
+
""" Set Cell Size in Scale. """
|
|
213
|
+
|
|
214
|
+
if isinstance(size_scale, tuple) and len(size_scale) == 2:
|
|
215
|
+
self._reset_cell_size()
|
|
216
|
+
self.default_row_height = self.default_row_height * size_scale[0]
|
|
217
|
+
self.default_col_width = self.default_col_width * size_scale[1]
|
|
218
|
+
|
|
219
|
+
for i in range(0, self.max_nrows):
|
|
220
|
+
worksheet.set_row_pixels(i, height=self.default_row_height)
|
|
221
|
+
|
|
222
|
+
worksheet.set_column_pixels(0, self.max_ncols - 1, width=self.default_col_width)
|
|
223
|
+
return 0
|
|
224
|
+
|
|
225
|
+
def get_curr_loc(self, toCell = False):
|
|
226
|
+
""" Get Current Location in worksheet. """
|
|
227
|
+
if toCell:
|
|
228
|
+
return xl_rowcol_to_cell(self.curr_row, self.curr_col)
|
|
229
|
+
return (self.curr_row, self.curr_col)
|
|
230
|
+
|
|
231
|
+
def set_border_line(self, worksheet, valuerange, border_line = 1):
|
|
232
|
+
""" Set border line for a range of cells. """
|
|
233
|
+
border_fmt = self.workbook.add_format({'bottom': border_line, 'top': border_line, 'left': border_line, 'right': border_line})
|
|
234
|
+
self.set_cell_format(worksheet = worksheet, cell_range = valuerange, cformat = border_fmt)
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
def merge_col(self, worksheet, loc = None, nrows = 1, ncols = 1, text = "", skipby = 'row', cformat = 'BLUE_H4', retCellRange = None):
|
|
238
|
+
""" Merge columns in a single row. """
|
|
239
|
+
|
|
240
|
+
start_row = loc[0] if loc else self.curr_row
|
|
241
|
+
start_col = loc[1] if loc else self.curr_col
|
|
242
|
+
written_range = [start_row, start_col, start_row + nrows - 1, start_col + ncols - 1]
|
|
243
|
+
|
|
244
|
+
worksheet.merge_range(*written_range, text, self.dict_cell_format[cformat])
|
|
245
|
+
|
|
246
|
+
if self.verbose:
|
|
247
|
+
logging.info(f"Merged Cells: {self.to_cell_range_text(*written_range)}")
|
|
248
|
+
|
|
249
|
+
# Skipped by Rows/Columns
|
|
250
|
+
if skipby == 'row':
|
|
251
|
+
self.curr_row = (start_row + nrows + self.gap_number)
|
|
252
|
+
if skipby == 'col':
|
|
253
|
+
self.curr_col = (start_col + ncols + self.gap_number)
|
|
254
|
+
|
|
255
|
+
# Return written location by Cell Text/Value Range
|
|
256
|
+
if retCellRange == "text":
|
|
257
|
+
return self.to_cell_range_text(*written_range)
|
|
258
|
+
if retCellRange == "value":
|
|
259
|
+
return written_range
|
|
260
|
+
|
|
261
|
+
return 0
|
|
262
|
+
|
|
263
|
+
def write_dataframe(self, worksheet, df, loc = None, title = None, index = False, header = True, skipby = 'row', titleformat = "BLUE_H4", headerformat = "TABLE_HEADER", valueformat="----", retCellRange = None):
|
|
264
|
+
""" Write a dataframe to excel file. """
|
|
265
|
+
|
|
266
|
+
start_row = loc[0] if loc else self.curr_row
|
|
267
|
+
start_col = loc[1] if loc else self.curr_col
|
|
268
|
+
|
|
269
|
+
ncols = df.shape[1]
|
|
270
|
+
nrows = df.shape[0]
|
|
271
|
+
|
|
272
|
+
## Get Number of Index Columns
|
|
273
|
+
index_ncols = 0
|
|
274
|
+
if index:
|
|
275
|
+
index_ncols = len(df.index.names)
|
|
276
|
+
ncols += index_ncols
|
|
277
|
+
|
|
278
|
+
## Get Number of Header Rows
|
|
279
|
+
header_nrows = 0
|
|
280
|
+
if header:
|
|
281
|
+
header_nrows = len(df.columns.names)
|
|
282
|
+
nrows += header_nrows
|
|
283
|
+
|
|
284
|
+
## Get Number of Title Rows
|
|
285
|
+
title_nrows = 0
|
|
286
|
+
if title:
|
|
287
|
+
title_nrows = 1
|
|
288
|
+
nrows += title_nrows
|
|
289
|
+
|
|
290
|
+
## Get Header Range (Include Index)
|
|
291
|
+
if header:
|
|
292
|
+
header_start_row = (start_row + title_nrows)
|
|
293
|
+
header_start_col = start_col
|
|
294
|
+
header_end_row = max((start_row + header_nrows - 1), header_start_row)
|
|
295
|
+
header_end_col = (start_col + ncols - 1)
|
|
296
|
+
header_range = self.to_cell_range_text(header_start_row, header_start_col,
|
|
297
|
+
header_end_row, header_end_col)
|
|
298
|
+
|
|
299
|
+
## Write DataFrame
|
|
300
|
+
if title:
|
|
301
|
+
self.merge_col(worksheet = worksheet, loc = loc, ncols = ncols, cformat=titleformat,
|
|
302
|
+
text = title, skipby = None)
|
|
303
|
+
df.to_excel(self.engine, sheet_name = worksheet.name, startrow = start_row + title_nrows,
|
|
304
|
+
startcol = start_col, header = header, index = index)
|
|
305
|
+
else:
|
|
306
|
+
df.to_excel(self.engine, sheet_name = worksheet.name, startrow = start_row,
|
|
307
|
+
startcol = start_col, header = header, index = index)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
## Get Value Range (Include Index)
|
|
311
|
+
value_start_row = (start_row + title_nrows + header_nrows)
|
|
312
|
+
value_start_col = start_col
|
|
313
|
+
value_end_row = (start_row + nrows - 1)
|
|
314
|
+
value_end_col = (start_col + ncols - 1)
|
|
315
|
+
value_range = [value_start_row, value_start_col, value_end_row, value_end_col]
|
|
316
|
+
|
|
317
|
+
## Set Format
|
|
318
|
+
self.set_cell_format(worksheet = worksheet, cell_range = value_range, cformat = valueformat)
|
|
319
|
+
if header:
|
|
320
|
+
self.set_cell_format(worksheet = worksheet, cell_range = header_range, cformat = headerformat)
|
|
321
|
+
|
|
322
|
+
written_range = [start_row, start_col, (start_row + nrows - 1), (start_col + ncols - 1)]
|
|
323
|
+
|
|
324
|
+
if self.verbose:
|
|
325
|
+
logging.info(f"Table Written in Cell Range: {self.to_cell_range_text(*written_range)}")
|
|
326
|
+
|
|
327
|
+
if skipby == 'row':
|
|
328
|
+
self.curr_row = (start_row + nrows + self.gap_number)
|
|
329
|
+
if skipby == 'col':
|
|
330
|
+
self.curr_col = (start_col + ncols + self.gap_number)
|
|
331
|
+
|
|
332
|
+
if retCellRange == "text":
|
|
333
|
+
return self.to_cell_range_text(*written_range)
|
|
334
|
+
if retCellRange == "value":
|
|
335
|
+
return written_range
|
|
336
|
+
|
|
337
|
+
return 0
|
|
338
|
+
|
|
339
|
+
def _get_image_size(self, figPath, figScale = (1, 1), retSizeInCell = True):
|
|
340
|
+
""" Get Image Size in Excel Cells. """
|
|
341
|
+
img = Image.open(figPath)
|
|
342
|
+
w, h = img.size
|
|
343
|
+
(width, height) = (img.width * figScale[0], img.height * figScale[1])
|
|
344
|
+
h_in_cell = np.floor(height/self.default_row_height)
|
|
345
|
+
w_in_cell = np.ceil(width/self.default_col_width)
|
|
346
|
+
img.close()
|
|
347
|
+
if retSizeInCell:
|
|
348
|
+
return (w_in_cell, h_in_cell)
|
|
349
|
+
return (width, height)
|
|
350
|
+
|
|
351
|
+
def _resize_image(self, imgPath, resize, outPath, size_in_cell = True):
|
|
352
|
+
""" Resize image. """
|
|
353
|
+
# Open the image
|
|
354
|
+
image = Image.open(imgPath)
|
|
355
|
+
new_image = image.resize((resize[0], resize[1]))
|
|
356
|
+
if size_in_cell:
|
|
357
|
+
# Resize the image
|
|
358
|
+
new_image = image.resize((resize[1] * self.default_col_width, resize[0] * self.default_row_height))
|
|
359
|
+
# Save the resized image
|
|
360
|
+
new_image.save(outPath)
|
|
361
|
+
image.close()
|
|
362
|
+
return 0
|
|
363
|
+
|
|
364
|
+
def insert_image(self, worksheet, figPath, figScale = (1, 1), loc = None, skipby = 'row', retCellRange = None):
|
|
365
|
+
""" Insert an image to the sheet. """
|
|
366
|
+
start_row = loc[0] if loc else self.curr_row
|
|
367
|
+
start_col = loc[1] if loc else self.curr_col
|
|
368
|
+
|
|
369
|
+
start_cell = xl_rowcol_to_cell(start_row, start_col)
|
|
370
|
+
figsize_in_cells = self._get_image_size(figPath = figPath, figScale = figScale, retSizeInCell = True)
|
|
371
|
+
|
|
372
|
+
worksheet.insert_image(start_cell, figPath, {"x_scale": figScale[0], "y_scale": figScale[1]})
|
|
373
|
+
|
|
374
|
+
# Skipped by Rows/Columns
|
|
375
|
+
if skipby == 'row':
|
|
376
|
+
self.curr_row = (start_row + int(figsize_in_cells[1]) + self.gap_number)
|
|
377
|
+
if skipby == 'col':
|
|
378
|
+
self.curr_col = (start_col + int(figsize_in_cells[0]) + self.gap_number)
|
|
379
|
+
|
|
380
|
+
written_range = [start_row, start_col,
|
|
381
|
+
start_row + int(figsize_in_cells[1]),
|
|
382
|
+
start_col + int(figsize_in_cells[0])]
|
|
383
|
+
|
|
384
|
+
# Return written location by Cell Text/Value Range
|
|
385
|
+
if retCellRange == "text":
|
|
386
|
+
return self.to_cell_range_text(*written_range)
|
|
387
|
+
if retCellRange == "value":
|
|
388
|
+
return written_range
|
|
389
|
+
|
|
390
|
+
if self.verbose:
|
|
391
|
+
logging.info(f"Image Written in Cell Range: {self.to_cell_range_text(*written_range)}")
|
|
392
|
+
|
|
393
|
+
return 0
|
|
394
|
+
|
|
395
|
+
def __add_chart_data_tab(self, tabname, hide = True, max_num = 99999):
|
|
396
|
+
""" Add a temp tab for chart data. """
|
|
397
|
+
i = 0
|
|
398
|
+
while i <= max_num:
|
|
399
|
+
tabname_i = (tabname + str(i))
|
|
400
|
+
if tabname_i not in self.ws_dict:
|
|
401
|
+
ws = self.add_worksheet(tabname_i, reset_loc = False,
|
|
402
|
+
cell_scale = None,
|
|
403
|
+
auto_fit = False,
|
|
404
|
+
zoom_perc = 100)
|
|
405
|
+
ws.hide()
|
|
406
|
+
return ws
|
|
407
|
+
i += 1
|
|
408
|
+
return -1
|
|
409
|
+
|
|
410
|
+
def _transpose_df_for_chart(self, df, y_list, x = None):
|
|
411
|
+
""" Transpose Dataframe for Chart Structrue. """
|
|
412
|
+
|
|
413
|
+
_df = df.copy()
|
|
414
|
+
_df.columns.name = None
|
|
415
|
+
_df.index.name = None
|
|
416
|
+
|
|
417
|
+
if x is None:
|
|
418
|
+
_df = _df[y_list].T
|
|
419
|
+
_df = _df.reset_index(drop=False)
|
|
420
|
+
else:
|
|
421
|
+
x = x if isinstance(x, list) else [x]
|
|
422
|
+
_df = tanspose_dataframe(_df[[*x, *y_list]], x)
|
|
423
|
+
|
|
424
|
+
_df.columns.name = None
|
|
425
|
+
_df.index.name = None
|
|
426
|
+
return _df
|
|
427
|
+
|
|
428
|
+
def __convert_to_none_tuple(self, x = None):
|
|
429
|
+
""" Convert None Type to Tuple of Nones. """
|
|
430
|
+
if x is None:
|
|
431
|
+
return (None, None)
|
|
432
|
+
return x
|
|
433
|
+
|
|
434
|
+
def __validate_input_chart_obj(self, chart_type, input_chart):
|
|
435
|
+
""" Validate if the given append_to_chart object is aligned with chart_type argument. """
|
|
436
|
+
|
|
437
|
+
if chart_type == "line":
|
|
438
|
+
return isinstance(input_chart, xlsxwriter.chart_line.ChartLine)
|
|
439
|
+
if chart_type in ["column", "stacked_column"]:
|
|
440
|
+
return isinstance(input_chart, xlsxwriter.chart_column.ChartColumn)
|
|
441
|
+
if chart_type == "pie":
|
|
442
|
+
return isinstance(input_chart, xlsxwriter.chart_pie.ChartPie)
|
|
443
|
+
|
|
444
|
+
def write_chart(self, worksheet, df, y_list, x = None, title = "",
|
|
445
|
+
chart_size = (30, 13), chart_type = "line",
|
|
446
|
+
y_axis_range = (None, None), y_num_format = None,
|
|
447
|
+
y2_axis = False, loc = None, retChart = False,
|
|
448
|
+
skipby = "row", outputData = False, retCellRange = None,
|
|
449
|
+
xy_axes_name = ("", ""), major_gridlines = False,
|
|
450
|
+
legend = "bottom", line_marker = "circle", line_type = "solid",
|
|
451
|
+
chart_style = None, append_to_chart = None):
|
|
452
|
+
""" Write line chart to Excel worksheet."""
|
|
453
|
+
|
|
454
|
+
if isinstance(chart_type, str):
|
|
455
|
+
if chart_type == "line":
|
|
456
|
+
chart_type = {'type': 'line'}
|
|
457
|
+
elif chart_type == "column":
|
|
458
|
+
chart_type = {'type': 'column'}
|
|
459
|
+
elif chart_type == "stacked_column":
|
|
460
|
+
chart_type = {'type': 'column', "subtype": "stacked"}
|
|
461
|
+
elif chart_type == "pie":
|
|
462
|
+
chart_type = {"type": "pie"}
|
|
463
|
+
else:
|
|
464
|
+
raise ValueError(" Please select one from 'line', 'column', 'stacked_column' or 'pie'. ")
|
|
465
|
+
|
|
466
|
+
start_row = loc[0] if loc else self.curr_row
|
|
467
|
+
start_col = loc[1] if loc else self.curr_col
|
|
468
|
+
|
|
469
|
+
y_axis_range = self.__convert_to_none_tuple(y_axis_range)
|
|
470
|
+
|
|
471
|
+
df_t = self._transpose_df_for_chart(df = df, y_list = y_list, x = x)
|
|
472
|
+
if x is None:
|
|
473
|
+
x = list(df.index.names)
|
|
474
|
+
if isinstance(x, str):
|
|
475
|
+
x = [x]
|
|
476
|
+
y = "index" if len(x) == 1 else tuple(["index"] + [''] * (len(x) - 1))
|
|
477
|
+
x_list = [x for x in df_t.columns if x != y]
|
|
478
|
+
|
|
479
|
+
data_nrows = df_t.shape[0]
|
|
480
|
+
data_ncols = df_t.shape[1]
|
|
481
|
+
|
|
482
|
+
raw_data_ws = self.__add_chart_data_tab("__CHRT_DATA_")
|
|
483
|
+
|
|
484
|
+
## if y not given, then use index value as y series.
|
|
485
|
+
data_index_as_y = True
|
|
486
|
+
if y is None:
|
|
487
|
+
sel_df = df_t[[*x_list]]
|
|
488
|
+
else:
|
|
489
|
+
sel_df = df_t[[y, *x_list]]
|
|
490
|
+
sel_df = sel_df.set_index(y)
|
|
491
|
+
sel_df.index.name = None
|
|
492
|
+
|
|
493
|
+
## if output chart data to current worksheet.
|
|
494
|
+
if outputData:
|
|
495
|
+
raw_data_ws = worksheet
|
|
496
|
+
df_range = self.write_dataframe(worksheet = raw_data_ws, df = sel_df, index = data_index_as_y, title = None, skipby=None, retCellRange="value", loc = (start_row, start_col))
|
|
497
|
+
chart_loc = (df_range[2] + self.gap_number, df_range[1])
|
|
498
|
+
else:
|
|
499
|
+
df_range = self.write_dataframe(worksheet = raw_data_ws, df = sel_df, index = data_index_as_y, title = None, skipby=None, retCellRange="value", loc = (0,0))
|
|
500
|
+
chart_loc = (start_row, start_col)
|
|
501
|
+
|
|
502
|
+
x_count = len(x_list)
|
|
503
|
+
nrows = df_t.shape[0]
|
|
504
|
+
|
|
505
|
+
data_row_anchor = df_range[0]
|
|
506
|
+
data_col_anchor = df_range[1]
|
|
507
|
+
row_shift = 0
|
|
508
|
+
|
|
509
|
+
category_range = [df_range[0], df_range[1] + 1,
|
|
510
|
+
df_range[0] + (len(x) - 1), df_range[3]]
|
|
511
|
+
|
|
512
|
+
if self.verbose:
|
|
513
|
+
logging.info(f"Category Cell Range: {self.to_cell_range_text(*category_range)}")
|
|
514
|
+
|
|
515
|
+
chart = self.workbook.add_chart(chart_type)
|
|
516
|
+
if append_to_chart is not None:
|
|
517
|
+
if self.__validate_input_chart_obj(chart_type = chart_type["type"], input_chart = append_to_chart):
|
|
518
|
+
chart = append_to_chart
|
|
519
|
+
else:
|
|
520
|
+
warnings.warn("WARNING: Can only append data to the chart that has the same chart type as your given one.")
|
|
521
|
+
|
|
522
|
+
value_start = len(x) + 1 if len(x) > 1 else len(x)
|
|
523
|
+
for row_shift in range(value_start, value_start + nrows, 1):
|
|
524
|
+
|
|
525
|
+
value_range = [data_row_anchor + row_shift, data_col_anchor + 1,
|
|
526
|
+
data_row_anchor + row_shift, data_col_anchor + x_count]
|
|
527
|
+
|
|
528
|
+
chart.add_series({
|
|
529
|
+
'name': [raw_data_ws.name, (data_row_anchor + row_shift), data_col_anchor],
|
|
530
|
+
'categories': [raw_data_ws.name, *category_range],
|
|
531
|
+
'values': [raw_data_ws.name, *value_range],
|
|
532
|
+
'marker': {'type': line_marker},
|
|
533
|
+
"line" : {'dash_type': line_type},
|
|
534
|
+
'data_labels': {'percentage': True} if chart_type['type'] == 'pie' else None,
|
|
535
|
+
'y2_axis': y2_axis,
|
|
536
|
+
})
|
|
537
|
+
if self.verbose:
|
|
538
|
+
logging.info("Values Cell Range: %s", self.to_cell_range_text(*value_range))
|
|
539
|
+
|
|
540
|
+
chart.set_title({'name': title})
|
|
541
|
+
chart.set_legend({'position': str(legend)})
|
|
542
|
+
chart.set_size({"height": chart_size[0] * self.default_row_height, # by row 15
|
|
543
|
+
"width": chart_size[1] * self.default_col_width}) # by col 8.43
|
|
544
|
+
|
|
545
|
+
chart.set_x_axis({"name": xy_axes_name[0]})
|
|
546
|
+
|
|
547
|
+
set_y_axis_dict = {"name": xy_axes_name[1],
|
|
548
|
+
"major_gridlines": {"visible": int(major_gridlines)},
|
|
549
|
+
'min': y_axis_range[0],
|
|
550
|
+
'max': y_axis_range[1],
|
|
551
|
+
'num_format': y_num_format}
|
|
552
|
+
if y2_axis:
|
|
553
|
+
chart.set_y2_axis(set_y_axis_dict)
|
|
554
|
+
else:
|
|
555
|
+
chart.set_y_axis(set_y_axis_dict)
|
|
556
|
+
|
|
557
|
+
# Setting Chart Style
|
|
558
|
+
if chart_style:
|
|
559
|
+
chart.set_style(chart_style)
|
|
560
|
+
|
|
561
|
+
# Need to Return Chart before Insert it to the worksheet.
|
|
562
|
+
if retChart:
|
|
563
|
+
return chart
|
|
564
|
+
|
|
565
|
+
worksheet.insert_chart(
|
|
566
|
+
chart_loc[0],
|
|
567
|
+
chart_loc[1],
|
|
568
|
+
chart
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
written_range = [start_row, start_col, int(chart_loc[0] + chart_size[0]), int(chart_loc[1] + chart_size[1])]
|
|
572
|
+
if outputData:
|
|
573
|
+
|
|
574
|
+
written_range = [start_row, start_col,
|
|
575
|
+
(start_row + data_nrows + self.gap_number + chart_size[0]),
|
|
576
|
+
max(df_range[3], start_col + chart_size[1])]
|
|
577
|
+
|
|
578
|
+
if skipby == 'row':
|
|
579
|
+
if outputData:
|
|
580
|
+
self.curr_row = (start_row + data_nrows + self.gap_number + chart_size[0] + self.gap_number - 1)
|
|
581
|
+
else:
|
|
582
|
+
self.curr_row = (start_row + chart_size[0] + self.gap_number - 1)
|
|
583
|
+
if skipby == 'col':
|
|
584
|
+
self.curr_col = (start_col + int(chart_size[1]) + self.gap_number)
|
|
585
|
+
|
|
586
|
+
if self.verbose:
|
|
587
|
+
logging.info(f"Chart Written in Cell Range: {self.to_cell_range_text(*written_range)}")
|
|
588
|
+
|
|
589
|
+
if retCellRange == "text":
|
|
590
|
+
return self.to_cell_range_text(*written_range)
|
|
591
|
+
if retCellRange == "value":
|
|
592
|
+
return written_range
|
|
593
|
+
|
|
594
|
+
return 0
|
|
595
|
+
|
|
596
|
+
def write_combined_chart(self, worksheet, chart1, chart2,
|
|
597
|
+
loc = None, chart_size = (30, 13),
|
|
598
|
+
skipby = "row", retCellRange = None):
|
|
599
|
+
""" Combined two chart objects and then write to Excel worksheet. """
|
|
600
|
+
|
|
601
|
+
start_row = loc[0] if loc else self.curr_row
|
|
602
|
+
start_col = loc[1] if loc else self.curr_col
|
|
603
|
+
|
|
604
|
+
chart1.combine(chart2)
|
|
605
|
+
worksheet.insert_chart(xl_rowcol_to_cell(start_row, start_col), chart1)
|
|
606
|
+
|
|
607
|
+
written_range = [start_row, start_col, (start_row + chart_size[0]), (start_col + chart_size[1])]
|
|
608
|
+
|
|
609
|
+
if skipby == 'row':
|
|
610
|
+
self.curr_row = (start_row + chart_size[0] + self.gap_number - 1)
|
|
611
|
+
if skipby == 'col':
|
|
612
|
+
self.curr_col = (start_col + chart_size[1] + self.gap_number)
|
|
613
|
+
|
|
614
|
+
if self.verbose:
|
|
615
|
+
logging.info(f"Chart Written in Cell Range: {self.to_cell_range_text(*written_range)}")
|
|
616
|
+
|
|
617
|
+
if retCellRange == "text":
|
|
618
|
+
return self.to_cell_range_text(*written_range)
|
|
619
|
+
if retCellRange == "value":
|
|
620
|
+
return written_range
|
|
621
|
+
|
|
622
|
+
return 0
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def write_duo_chart(self, worksheet, df,
|
|
626
|
+
y1_list, y2_list = None, x = None,
|
|
627
|
+
c1_type = "column", c2_type = "line",
|
|
628
|
+
y1_axis_range = (0, 1), y2_axis_range = None,
|
|
629
|
+
y1_num_format = None, y2_num_format = None,
|
|
630
|
+
y1_line_marker = "circle", y2_line_marker = "circle",
|
|
631
|
+
y1_line_type = "solid", y2_line_type = "solid",
|
|
632
|
+
loc = None, title = "", chart_size = (30, 13),
|
|
633
|
+
xy_axes_name = ("", ""), major_gridlines = False,
|
|
634
|
+
retChart = False, retCellRange = None,
|
|
635
|
+
skipby = "row"):
|
|
636
|
+
""" Write duo-chart sharing the same x axis. """
|
|
637
|
+
|
|
638
|
+
start_row = loc[0] if loc else self.curr_row
|
|
639
|
+
start_col = loc[1] if loc else self.curr_col
|
|
640
|
+
|
|
641
|
+
y2_axis = True
|
|
642
|
+
if y2_list is None:
|
|
643
|
+
y2_axis = False
|
|
644
|
+
|
|
645
|
+
if y2_axis_range is None:
|
|
646
|
+
y2_axis_range = y1_axis_range
|
|
647
|
+
|
|
648
|
+
xy_name = (xy_axes_name[0], xy_axes_name[1])
|
|
649
|
+
xy2_name = (xy_axes_name[0], xy_axes_name[1])
|
|
650
|
+
if y2_axis and len(xy_axes_name) >= 3:
|
|
651
|
+
xy_name = (xy_axes_name[0], xy_axes_name[1])
|
|
652
|
+
xy2_name = (xy_axes_name[0], xy_axes_name[2])
|
|
653
|
+
if y2_axis and len(xy_axes_name) < 3:
|
|
654
|
+
xy_name = (xy_axes_name[0], xy_axes_name[1])
|
|
655
|
+
xy2_name = (xy_axes_name[0], "")
|
|
656
|
+
|
|
657
|
+
chart1 = self.write_chart(df = df,
|
|
658
|
+
x = x,
|
|
659
|
+
y_list = y1_list,
|
|
660
|
+
worksheet = worksheet,
|
|
661
|
+
title = title,
|
|
662
|
+
chart_type = c1_type,
|
|
663
|
+
chart_size = chart_size,
|
|
664
|
+
y_axis_range = y1_axis_range,
|
|
665
|
+
xy_axes_name = xy_name,
|
|
666
|
+
major_gridlines = major_gridlines,
|
|
667
|
+
y_num_format = y1_num_format,
|
|
668
|
+
line_type = y1_line_type,
|
|
669
|
+
line_marker = y1_line_marker,
|
|
670
|
+
retChart = True)
|
|
671
|
+
|
|
672
|
+
chart2 = self.write_chart(df = df,
|
|
673
|
+
x = x,
|
|
674
|
+
y_list = y2_list,
|
|
675
|
+
worksheet=worksheet,
|
|
676
|
+
title = title,
|
|
677
|
+
chart_type = c2_type,
|
|
678
|
+
chart_size = chart_size,
|
|
679
|
+
y_axis_range = y2_axis_range,
|
|
680
|
+
y2_axis = y2_axis,
|
|
681
|
+
xy_axes_name = xy2_name,
|
|
682
|
+
major_gridlines = False,
|
|
683
|
+
y_num_format = y2_num_format,
|
|
684
|
+
line_type = y2_line_type,
|
|
685
|
+
line_marker = y2_line_marker,
|
|
686
|
+
retChart=True)
|
|
687
|
+
|
|
688
|
+
# Need to Return Chart before Insert it to the worksheet.
|
|
689
|
+
if retChart:
|
|
690
|
+
return (chart1, chart2)
|
|
691
|
+
|
|
692
|
+
cell_range = self.write_combined_chart(worksheet,
|
|
693
|
+
chart1, chart2,
|
|
694
|
+
loc = loc, chart_size = chart_size,
|
|
695
|
+
skipby = skipby, retCellRange = retCellRange)
|
|
696
|
+
|
|
697
|
+
return cell_range
|
|
698
|
+
|
|
699
|
+
def write_text_by_dict(self, worksheet, dict_cells):
|
|
700
|
+
""" Write text using Python dictionary. """
|
|
701
|
+
for cell in dict_cells:
|
|
702
|
+
list_contents = dict_cells[cell]
|
|
703
|
+
|
|
704
|
+
if ':' in cell:
|
|
705
|
+
text, cell_format = list_contents
|
|
706
|
+
worksheet.merge_range(cell, text, self.dict_cell_format[cell_format])
|
|
707
|
+
|
|
708
|
+
else:
|
|
709
|
+
if len(list_contents) == 1:
|
|
710
|
+
list_items = []
|
|
711
|
+
|
|
712
|
+
# Text formats begin with '~~~'
|
|
713
|
+
list_mixed_items = list_contents[0]
|
|
714
|
+
for item in list_mixed_items:
|
|
715
|
+
if item.startswith('~~~'):
|
|
716
|
+
cell_format = item.split('~~~')[1]
|
|
717
|
+
item = self.dict_cell_format[cell_format]
|
|
718
|
+
|
|
719
|
+
list_items.append(item)
|
|
720
|
+
else:
|
|
721
|
+
list_items.append(item)
|
|
722
|
+
|
|
723
|
+
worksheet.write_rich_string(cell, *list_items)
|
|
724
|
+
|
|
725
|
+
else:
|
|
726
|
+
text, cell_format = list_contents
|
|
727
|
+
worksheet.write(cell, text, self.dict_cell_format[cell_format])
|
|
728
|
+
|
|
729
|
+
def __split_line_by_format_sign(self, line):
|
|
730
|
+
""" To split line by specified format sign '{}'. """
|
|
731
|
+
|
|
732
|
+
## find all format sign '{}'
|
|
733
|
+
format_sign = re.findall(".*?{(.*?)}.*?", line)
|
|
734
|
+
format_sign = format_sign if len(format_sign) != 0 else ['']
|
|
735
|
+
format_sign = [x.lstrip().rstrip() for x in format_sign]
|
|
736
|
+
|
|
737
|
+
## find all cell formats
|
|
738
|
+
cell_format_sign = re.findall(".*?\[\[(.*?)\]\]$", line.strip().replace("\n", ""))
|
|
739
|
+
cell_format_sign = [x.lstrip().rstrip() for x in cell_format_sign]
|
|
740
|
+
|
|
741
|
+
## clean up text by removing cell format express
|
|
742
|
+
if len(cell_format_sign) > 0:
|
|
743
|
+
line = line.split("[[")[0]
|
|
744
|
+
|
|
745
|
+
## find text before the first format sign appeared.
|
|
746
|
+
text_bf_curly = re.findall(r"(.*?)\{", line)
|
|
747
|
+
text_bf_curly = [text_bf_curly[0]] if len(text_bf_curly) != 0 else ['']
|
|
748
|
+
text_bf_curly = [x.strip("\n") for x in text_bf_curly]
|
|
749
|
+
|
|
750
|
+
## find text after the first format sign appeared.
|
|
751
|
+
text_af_curly = re.findall(r"\}\s*(.*?)(?=\s*\{|$)", line) if re.search(".*?{(.*?)}.*?", line) else [line]
|
|
752
|
+
text_af_curly = [x.strip("\n") for x in text_af_curly]
|
|
753
|
+
|
|
754
|
+
return text_bf_curly, format_sign, text_af_curly, cell_format_sign
|
|
755
|
+
|
|
756
|
+
def __parse_line_by_format_sign(self, worksheet, loc, line):
|
|
757
|
+
""" Parse text by format sign. """
|
|
758
|
+
|
|
759
|
+
text_bf_curly, format_sign, text_af_curly, cell_format_sign = self.__split_line_by_format_sign(line)
|
|
760
|
+
|
|
761
|
+
start_text = text_bf_curly[0]
|
|
762
|
+
# if len(start_text) > 0 or len(format_sign) >= 2:
|
|
763
|
+
if len(format_sign) >= 2:
|
|
764
|
+
# if one line has more than 2 formats specified.
|
|
765
|
+
|
|
766
|
+
res = [loc, start_text] if start_text != '' else [loc]
|
|
767
|
+
assert len(format_sign) == len(text_af_curly)
|
|
768
|
+
|
|
769
|
+
for fmt, s2 in zip(format_sign, text_af_curly):
|
|
770
|
+
res.append(self.dict_cell_format[fmt] if fmt else self.dict_cell_format[""])
|
|
771
|
+
res.append(s2.strip('\n')) # remove enter sign
|
|
772
|
+
|
|
773
|
+
# Write line by line
|
|
774
|
+
if self.verbose:
|
|
775
|
+
logging.info(res)
|
|
776
|
+
|
|
777
|
+
worksheet.write_rich_string(*res)
|
|
778
|
+
|
|
779
|
+
if len(format_sign) < 2:
|
|
780
|
+
# if one line has more than 2 formats specified.
|
|
781
|
+
|
|
782
|
+
res = [loc, start_text] if start_text != '' else [loc]
|
|
783
|
+
assert len(format_sign) == len(text_af_curly)
|
|
784
|
+
|
|
785
|
+
fmt = format_sign[0]
|
|
786
|
+
text = text_af_curly[0].strip("\n")
|
|
787
|
+
res.append(text)
|
|
788
|
+
res.append(self.dict_cell_format[fmt])
|
|
789
|
+
|
|
790
|
+
# Write line by line
|
|
791
|
+
if self.verbose:
|
|
792
|
+
logging.info(res)
|
|
793
|
+
|
|
794
|
+
worksheet.write_string(*res)
|
|
795
|
+
|
|
796
|
+
if len(cell_format_sign) > 0:
|
|
797
|
+
self.set_cell_format(worksheet = worksheet, cell_range = loc, cformat = cell_format_sign[0])
|
|
798
|
+
return 0
|
|
799
|
+
|
|
800
|
+
def write_text_content(self, worksheet, input_text = None, txt_path = None, loc = None, retCellRange = None):
|
|
801
|
+
""" Write text content line by line to the worksheet. """
|
|
802
|
+
|
|
803
|
+
row_anchor = loc[0] if loc else self.curr_row
|
|
804
|
+
col_anchor = loc[1] if loc else self.curr_col
|
|
805
|
+
|
|
806
|
+
start_row = row_anchor
|
|
807
|
+
start_col = col_anchor
|
|
808
|
+
|
|
809
|
+
if (input_text) and (txt_path):
|
|
810
|
+
raise ValueError("Please give either input_text or txt_path, not both !!!")
|
|
811
|
+
|
|
812
|
+
if (input_text is None) and (txt_path is None):
|
|
813
|
+
raise ValueError("Please specify either input_text or txt_path.")
|
|
814
|
+
|
|
815
|
+
if txt_path is not None:
|
|
816
|
+
file_notes = open(txt_path, 'r')
|
|
817
|
+
textlines = file_notes.readlines()
|
|
818
|
+
|
|
819
|
+
if input_text is not None:
|
|
820
|
+
textlines = input_text.split("\n")
|
|
821
|
+
|
|
822
|
+
for line in textlines:
|
|
823
|
+
# Write text line by line
|
|
824
|
+
loc = xl_rowcol_to_cell(row_anchor, col_anchor)
|
|
825
|
+
|
|
826
|
+
if " [>] " in line:
|
|
827
|
+
line_split = line.split(" [>] ")
|
|
828
|
+
col_shift = 0
|
|
829
|
+
for sub_line in line_split:
|
|
830
|
+
sub_line = sub_line.rstrip()
|
|
831
|
+
self.__parse_line_by_format_sign(worksheet = worksheet, loc = loc, line = sub_line)
|
|
832
|
+
col_shift += 1
|
|
833
|
+
loc = xl_rowcol_to_cell(row_anchor, col_anchor + col_shift)
|
|
834
|
+
else:
|
|
835
|
+
self.__parse_line_by_format_sign(worksheet = worksheet, loc = loc, line = line)
|
|
836
|
+
|
|
837
|
+
row_anchor += 1
|
|
838
|
+
|
|
839
|
+
self.curr_row = (row_anchor - 1)
|
|
840
|
+
|
|
841
|
+
end_row = row_anchor
|
|
842
|
+
end_col = col_anchor
|
|
843
|
+
|
|
844
|
+
written_range = [start_row, start_col, end_row, end_col]
|
|
845
|
+
|
|
846
|
+
if self.verbose:
|
|
847
|
+
logging.info(f"Text Written in Cell Range: {self.to_cell_range_text(*written_range)}")
|
|
848
|
+
|
|
849
|
+
if retCellRange == "text":
|
|
850
|
+
return self.to_cell_range_text(*written_range)
|
|
851
|
+
if retCellRange == "value":
|
|
852
|
+
return written_range
|
|
853
|
+
|
|
854
|
+
return 0
|
|
855
|
+
|
|
856
|
+
@staticmethod
|
|
857
|
+
def plot_boxplot(df, x, y, y_percentage = False, colored_box = True, color_grp = (10, 1), title = "", fontsize = 14, figsize = (8, 6), show_fig = True, img_path = None, transp_bg = False):
|
|
858
|
+
""" Plot Box Plot Chart using matplotlib. """
|
|
859
|
+
|
|
860
|
+
plt.style.use('default')
|
|
861
|
+
|
|
862
|
+
fig, ax = plt.subplots(1, 1, figsize = figsize, dpi=200)
|
|
863
|
+
|
|
864
|
+
box_plot_data = convert_to_boxplot_data(df, x, y, True)
|
|
865
|
+
|
|
866
|
+
bplot = ax.boxplot(box_plot_data.values(), patch_artist=colored_box, widths = 0.4)
|
|
867
|
+
ax.set_xticklabels(box_plot_data.keys(), fontsize = fontsize)
|
|
868
|
+
ax.set_ylabel(string_proc(y), color='black', fontsize = fontsize)
|
|
869
|
+
ax.set_xlabel(string_proc(x), color='black', fontsize = fontsize)
|
|
870
|
+
ax.tick_params(axis='both', labelsize=fontsize)
|
|
871
|
+
ax.grid(True, 'major', 'y', ls='--', lw=.5, c='k', alpha=.3)
|
|
872
|
+
ax.set_title(title, fontsize=fontsize + 4)
|
|
873
|
+
|
|
874
|
+
if colored_box:
|
|
875
|
+
colors = color_input_validation(color_grp, len(box_plot_data.keys()))
|
|
876
|
+
for patch, color in zip(bplot['boxes'], colors):
|
|
877
|
+
patch.set_facecolor(color)
|
|
878
|
+
patch.set_alpha(.7)
|
|
879
|
+
|
|
880
|
+
if y_percentage:
|
|
881
|
+
plt.gca().yaxis.set_major_formatter(PercentFormatter(100))
|
|
882
|
+
plt.tight_layout()
|
|
883
|
+
|
|
884
|
+
if show_fig:
|
|
885
|
+
plt.show()
|
|
886
|
+
|
|
887
|
+
if img_path:
|
|
888
|
+
fig.savefig(img_path, transparent = transp_bg, dpi=200)
|
|
889
|
+
|
|
890
|
+
plt.close()
|
|
891
|
+
return fig, ax
|
|
892
|
+
|
|
893
|
+
def write_boxplot(self, ws, df, x, y, y_percentage = False, colored_box = True, color_grp = (10, 1), title = "",
|
|
894
|
+
fontsize = 14, figsize = (30, 13), show_fig = False, img_path = None, transp_bg = False,
|
|
895
|
+
loc = None, skipby = "row", retCellRange = None):
|
|
896
|
+
""" Write boxplot to Worksheet. """
|
|
897
|
+
|
|
898
|
+
currTime = getCurrentDateTime()
|
|
899
|
+
rn = str(random.randrange(0, 1000))
|
|
900
|
+
rn_num = currTime + rn
|
|
901
|
+
tmp_image_path = f"./.tmp_image_{rn_num}.png"
|
|
902
|
+
|
|
903
|
+
ExcelMaster.plot_boxplot(df = df,
|
|
904
|
+
x = x, y = y,
|
|
905
|
+
y_percentage = y_percentage,
|
|
906
|
+
colored_box = colored_box,
|
|
907
|
+
color_grp = color_grp,
|
|
908
|
+
title = title,
|
|
909
|
+
fontsize = fontsize,
|
|
910
|
+
show_fig = show_fig,
|
|
911
|
+
transp_bg = transp_bg,
|
|
912
|
+
img_path = tmp_image_path)
|
|
913
|
+
|
|
914
|
+
self._resize_image(tmp_image_path, figsize, tmp_image_path)
|
|
915
|
+
ret_range = self.insert_image(ws, figPath=tmp_image_path, figScale=(1, 1), loc = loc, skipby = skipby, retCellRange = retCellRange)
|
|
916
|
+
|
|
917
|
+
return ret_range
|