mergeron_extra 2024.739148.7__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.
- mergeron_extra/__init__.py +17 -0
- mergeron_extra/proportions_tests.py +512 -0
- mergeron_extra/py.types +0 -0
- mergeron_extra/tol_colors.py +846 -0
- mergeron_extra/xlsxw_helper.py +662 -0
- mergeron_extra-2024.739148.7.dist-info/METADATA +68 -0
- mergeron_extra-2024.739148.7.dist-info/RECORD +8 -0
- mergeron_extra-2024.739148.7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Methods for writing data from Python to fresh Excel workbooks using
|
|
3
|
+
the third-party package, `xlsxwriter`.
|
|
4
|
+
|
|
5
|
+
Includes a flexible system of defining cell formats.
|
|
6
|
+
|
|
7
|
+
NOTES
|
|
8
|
+
-----
|
|
9
|
+
|
|
10
|
+
This module is designed for producing formatted summary output. For
|
|
11
|
+
writing bulk data to Excel, facilities provided in third-party packages
|
|
12
|
+
such as `polars <https://pola.rs/>`_ likely provide better performance.
|
|
13
|
+
|
|
14
|
+
License
|
|
15
|
+
========
|
|
16
|
+
|
|
17
|
+
Copyright 2017-2023 S. Murthy Kambhampaty
|
|
18
|
+
Licese: MIT
|
|
19
|
+
https://mit-license.org/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections.abc import Sequence
|
|
27
|
+
from types import MappingProxyType
|
|
28
|
+
from typing import Any, ClassVar, Literal, TypeAlias, TypedDict, overload
|
|
29
|
+
|
|
30
|
+
import numpy as np
|
|
31
|
+
from aenum import Enum, extend_enum, unique # type: ignore
|
|
32
|
+
from numpy.typing import NDArray
|
|
33
|
+
from xlsxwriter.format import Format # type: ignore
|
|
34
|
+
from xlsxwriter.workbook import Workbook # type: ignore
|
|
35
|
+
from xlsxwriter.worksheet import Worksheet # type: ignore
|
|
36
|
+
|
|
37
|
+
from . import VERSION
|
|
38
|
+
|
|
39
|
+
__version__ = VERSION
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
XLBorderType: TypeAlias = Literal[
|
|
43
|
+
"none",
|
|
44
|
+
"thin",
|
|
45
|
+
"medium",
|
|
46
|
+
"dashed",
|
|
47
|
+
"dotted",
|
|
48
|
+
"thick",
|
|
49
|
+
"double",
|
|
50
|
+
"hair",
|
|
51
|
+
"medium_dashed",
|
|
52
|
+
"dash_dot",
|
|
53
|
+
"medium_dash_dot",
|
|
54
|
+
"dash_dot_dot",
|
|
55
|
+
"medium_dash_dot_dot",
|
|
56
|
+
"slant_dash_dot",
|
|
57
|
+
True,
|
|
58
|
+
False,
|
|
59
|
+
0,
|
|
60
|
+
1,
|
|
61
|
+
2,
|
|
62
|
+
3,
|
|
63
|
+
4,
|
|
64
|
+
5,
|
|
65
|
+
6,
|
|
66
|
+
7,
|
|
67
|
+
8,
|
|
68
|
+
9,
|
|
69
|
+
10,
|
|
70
|
+
11,
|
|
71
|
+
12,
|
|
72
|
+
13,
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CFmtVal(TypedDict, total=False):
|
|
77
|
+
"""Keys for xlsxwriter Format objects.
|
|
78
|
+
|
|
79
|
+
This is a partial list based on formats of interest.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
font_name: str
|
|
83
|
+
font_size: int
|
|
84
|
+
font_color: str
|
|
85
|
+
align: Literal[
|
|
86
|
+
"left", "center", "right", "center_across", "top", "bottom", "vcenter"
|
|
87
|
+
]
|
|
88
|
+
text_wrap: bool
|
|
89
|
+
rotation: int # integer, 0-360
|
|
90
|
+
indent: int
|
|
91
|
+
shrink: bool
|
|
92
|
+
bold: bool
|
|
93
|
+
italic: bool
|
|
94
|
+
underline: Literal[
|
|
95
|
+
True,
|
|
96
|
+
False,
|
|
97
|
+
1,
|
|
98
|
+
2,
|
|
99
|
+
33,
|
|
100
|
+
34,
|
|
101
|
+
"single",
|
|
102
|
+
"double",
|
|
103
|
+
"accountingSingle",
|
|
104
|
+
"accountingDouble",
|
|
105
|
+
]
|
|
106
|
+
font_strikeout: bool
|
|
107
|
+
font_script: Literal[1, 2]
|
|
108
|
+
|
|
109
|
+
num_format: str
|
|
110
|
+
|
|
111
|
+
pattern: int
|
|
112
|
+
fg_color: str # html color string, no #
|
|
113
|
+
bg_color: str # html color string, no #
|
|
114
|
+
|
|
115
|
+
hidden: bool
|
|
116
|
+
locked: bool
|
|
117
|
+
|
|
118
|
+
border: XLBorderType
|
|
119
|
+
bottom: XLBorderType
|
|
120
|
+
left: XLBorderType
|
|
121
|
+
right: XLBorderType
|
|
122
|
+
top: XLBorderType
|
|
123
|
+
border_color: str # html color string, no #
|
|
124
|
+
bottom_color: str # html color string, no #
|
|
125
|
+
left_color: str # html color string, no #
|
|
126
|
+
right_color: str # html color string, no #
|
|
127
|
+
top_color: str # html color string, no #
|
|
128
|
+
|
|
129
|
+
diag_border: XLBorderType
|
|
130
|
+
diag_border_color: str # html color string, no #
|
|
131
|
+
diag_type: Literal[
|
|
132
|
+
1, 2, 3, "up", "down", "left", "right", "cross", "diagonalUp", "diagonalDown"
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@unique
|
|
137
|
+
class CFmt(Enum): # type: ignore
|
|
138
|
+
"""
|
|
139
|
+
Cell format enums for xlsxwriter Format objects.
|
|
140
|
+
|
|
141
|
+
The enums defined here, or sequences of (any of) them
|
|
142
|
+
and any added with :meth:`CFmt.add_new`, are
|
|
143
|
+
rendered as :code:`xlsxWriter.Workbook.Format` objects
|
|
144
|
+
with :meth:`CFmt.xl_fmt`.
|
|
145
|
+
|
|
146
|
+
NOTES
|
|
147
|
+
-----
|
|
148
|
+
|
|
149
|
+
For more information about xlsxwriter cell formats,
|
|
150
|
+
see, https://xlsxwriter.readthedocs.io/format.html
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
XL_DEFAULT: ClassVar = MappingProxyType({"font_name": "Calibri", "font_size": 11})
|
|
155
|
+
XL_DEFAULT_2003: ClassVar = MappingProxyType({
|
|
156
|
+
"font_name": "Arial",
|
|
157
|
+
"font_size": 10,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
A_CTR: ClassVar = MappingProxyType({"align": "center"})
|
|
161
|
+
A_CTR_ACROSS: ClassVar = MappingProxyType({"align": "center_across"})
|
|
162
|
+
A_LEFT: ClassVar = MappingProxyType({"align": "left"})
|
|
163
|
+
A_RIGHT: ClassVar = MappingProxyType({"align": "right"})
|
|
164
|
+
V_TOP: ClassVar = MappingProxyType({"align": "top"})
|
|
165
|
+
V_BOTTOM: ClassVar = MappingProxyType({"align": "bottom"})
|
|
166
|
+
V_CTR: ClassVar = MappingProxyType({"align": "vcenter"})
|
|
167
|
+
|
|
168
|
+
TEXT_WRAP: ClassVar = MappingProxyType({"text_wrap": True})
|
|
169
|
+
TEXT_ROTATE: ClassVar = MappingProxyType({"rotation": 90})
|
|
170
|
+
IND_1: ClassVar = MappingProxyType({"indent": 1})
|
|
171
|
+
|
|
172
|
+
BOLD: ClassVar = MappingProxyType({"bold": True})
|
|
173
|
+
BOLD_ITALIC: ClassVar = MappingProxyType({"bold": True, "italic": True})
|
|
174
|
+
ITALIC: ClassVar = MappingProxyType({"italic": True})
|
|
175
|
+
ULINE: ClassVar = MappingProxyType({"underline": "single"})
|
|
176
|
+
SOUT: ClassVar = MappingProxyType({"font_strikeout": True})
|
|
177
|
+
# Useful with write_rich_text()
|
|
178
|
+
SUPERSCRIPT: ClassVar = MappingProxyType({"font_script": 1})
|
|
179
|
+
SUBSCRIPT: ClassVar = MappingProxyType({"font_script": 2})
|
|
180
|
+
|
|
181
|
+
AREA_NUM: ClassVar = MappingProxyType({"num_format": "0.00000000"})
|
|
182
|
+
DOLLAR_NUM: ClassVar = MappingProxyType({"num_format": "[$$-409]#,##0.00"})
|
|
183
|
+
DT_NUM: ClassVar = MappingProxyType({"num_format": "mm/dd/yyyy"})
|
|
184
|
+
PCT_NUM: ClassVar = MappingProxyType({"num_format": "##0%"})
|
|
185
|
+
PCT2_NUM: ClassVar = MappingProxyType({"num_format": "##0.00%"})
|
|
186
|
+
PCT4_NUM: ClassVar = MappingProxyType({"num_format": "##0.0000%"})
|
|
187
|
+
PCT6_NUM: ClassVar = MappingProxyType({"num_format": "##0.000000%"})
|
|
188
|
+
PCT8_NUM: ClassVar = MappingProxyType({"num_format": "##0.00000000%"})
|
|
189
|
+
QTY_NUM: ClassVar = MappingProxyType({"num_format": "#,##0.0"})
|
|
190
|
+
|
|
191
|
+
BAR_FILL: ClassVar = MappingProxyType({"pattern": 1, "bg_color": "dfeadf"})
|
|
192
|
+
HDR_FILL: ClassVar = MappingProxyType({"pattern": 1, "bg_color": "bfbfbf"})
|
|
193
|
+
|
|
194
|
+
FULL_BORDER: ClassVar = MappingProxyType({"border": 1, "border_color": "000000"})
|
|
195
|
+
BOTTOM_BORDER: ClassVar = MappingProxyType({"bottom": 1, "bottom_color": "000000"})
|
|
196
|
+
LEFT_BORDER: ClassVar = MappingProxyType({"left": 1, "left_color": "000000"})
|
|
197
|
+
RIGHT_BORDER: ClassVar = MappingProxyType({"right": 1, "right_color": "000000"})
|
|
198
|
+
TOP_BORDER: ClassVar = MappingProxyType({"top": 1, "top_color": "000000"})
|
|
199
|
+
HDR_BORDER: ClassVar = MappingProxyType(TOP_BORDER | BOTTOM_BORDER)
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def add_new(cls, _fmt_name: str, _xlsx_fmt_dict: CFmtVal, /) -> CFmt:
|
|
203
|
+
"""
|
|
204
|
+
Add new :class:`CFmt` object to instance.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
_fmt_name
|
|
209
|
+
Name of new member to be added to :class:`CFmt`
|
|
210
|
+
_xlsx_fmt_dict
|
|
211
|
+
Any valid argument to :code:`xlsxwriter.Workbook.add_format()`, or union of
|
|
212
|
+
same with the value of one or more :class:`CFmt` objects, e.g.,
|
|
213
|
+
:code:`CFmt.HDR_BORDER.value | CFmt.HDR_FILL.value` or
|
|
214
|
+
:code:`CFmt.HDR_BORDER.value | {"pattern": 1, "bg_color": "f2f2f2"}`
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
None
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
return extend_enum(cls, _fmt_name, MappingProxyType(_xlsx_fmt_dict)) # type: ignore
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def ensure_cell_format_spec_tuple(
|
|
226
|
+
cls, _cell_format: Sequence[CFmt | Sequence[CFmt]], /
|
|
227
|
+
) -> bool:
|
|
228
|
+
"""
|
|
229
|
+
Test that a given format specification is a tuple of :class:`CFmt` enums
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
_cell_format
|
|
234
|
+
Format specification
|
|
235
|
+
|
|
236
|
+
Raises
|
|
237
|
+
------
|
|
238
|
+
ValueError
|
|
239
|
+
If format specification is not a sequence of (sequences of)
|
|
240
|
+
:class:`CFmt` enums
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
True if format specification passes, else False
|
|
245
|
+
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
for _cf in _cell_format:
|
|
249
|
+
if isinstance(_cf, tuple):
|
|
250
|
+
cls.ensure_cell_format_spec_tuple(_cf)
|
|
251
|
+
|
|
252
|
+
if not (isinstance(_cf, CFmt),):
|
|
253
|
+
raise ValueError(
|
|
254
|
+
"Improperly specified format tuple for writing array."
|
|
255
|
+
" Must be tuple of :class:`CFmt` enums."
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
@classmethod
|
|
261
|
+
def xl_fmt(
|
|
262
|
+
cls,
|
|
263
|
+
_xl_book: Workbook,
|
|
264
|
+
_cell_format: Sequence[CFmt | Sequence[CFmt]] | CFmt | None,
|
|
265
|
+
/,
|
|
266
|
+
) -> Format:
|
|
267
|
+
"""
|
|
268
|
+
Return :code:`xlsxwriter` :code:`Format` object given a :class:`CFmt` enum, or tuple thereof.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
_xl_book
|
|
273
|
+
:code:`xlsxwriter.Workbook` object
|
|
274
|
+
|
|
275
|
+
_cell_format
|
|
276
|
+
:class:`CFmt` enum object, or tuple thereof
|
|
277
|
+
|
|
278
|
+
Raises
|
|
279
|
+
------
|
|
280
|
+
ValueError
|
|
281
|
+
If format specification is not one of None, a :class:`CFmt` enum, or
|
|
282
|
+
a :code:`Format` object
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
:code:`xlsxwriter` :code:`Format` object
|
|
287
|
+
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
if isinstance(_cell_format, Format):
|
|
291
|
+
return _cell_format
|
|
292
|
+
elif _cell_format is None:
|
|
293
|
+
return _xl_book.add_format(CFmt.XL_DEFAULT.value)
|
|
294
|
+
|
|
295
|
+
_cell_format_dict: CFmtVal = {}
|
|
296
|
+
if isinstance(_cell_format, Sequence):
|
|
297
|
+
cls.ensure_cell_format_spec_tuple(_cell_format)
|
|
298
|
+
for _cf in _cell_format:
|
|
299
|
+
if isinstance(_cf, Sequence):
|
|
300
|
+
for _cfi in _cf:
|
|
301
|
+
_cell_format_dict |= _cfi.value
|
|
302
|
+
else:
|
|
303
|
+
_cell_format_dict |= _cf.value
|
|
304
|
+
elif isinstance(_cell_format, CFmt):
|
|
305
|
+
_cell_format_dict = _cell_format.value
|
|
306
|
+
else:
|
|
307
|
+
raise ValueError("Improperly specified format specification.")
|
|
308
|
+
|
|
309
|
+
return _xl_book.add_format(_cell_format_dict)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def write_header(
|
|
313
|
+
_xl_sheet: Worksheet,
|
|
314
|
+
/,
|
|
315
|
+
*,
|
|
316
|
+
center_header: str | None = None,
|
|
317
|
+
left_header: str | None = None,
|
|
318
|
+
right_header: str | None = None,
|
|
319
|
+
) -> None:
|
|
320
|
+
"""Write header text to given worksheet.
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
----------
|
|
324
|
+
_xl_sheet
|
|
325
|
+
Worksheet object
|
|
326
|
+
center_header
|
|
327
|
+
Text for center header
|
|
328
|
+
left_header
|
|
329
|
+
Text for left header
|
|
330
|
+
right_header
|
|
331
|
+
Text for right header
|
|
332
|
+
|
|
333
|
+
Raises
|
|
334
|
+
------
|
|
335
|
+
ValueError
|
|
336
|
+
Must specify at least one header
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
None
|
|
341
|
+
"""
|
|
342
|
+
if any((center_header, left_header, right_header)):
|
|
343
|
+
_xl_sheet.set_header(
|
|
344
|
+
"".join([
|
|
345
|
+
f"&L{left_header}" if left_header else "",
|
|
346
|
+
f"&C{center_header}" if center_header else "",
|
|
347
|
+
f"&R{right_header}" if right_header else "",
|
|
348
|
+
])
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
else:
|
|
352
|
+
raise ValueError("must specify at least one header")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def write_footer(
|
|
356
|
+
_xl_sheet: Worksheet,
|
|
357
|
+
/,
|
|
358
|
+
*,
|
|
359
|
+
center_footer: str | None = None,
|
|
360
|
+
left_footer: str | None = None,
|
|
361
|
+
right_footer: str | None = None,
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Write footer text to given worksheet.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
_xl_sheet
|
|
368
|
+
Worksheet object
|
|
369
|
+
center_footer
|
|
370
|
+
Text for center footer
|
|
371
|
+
left_footer
|
|
372
|
+
Text for left footer
|
|
373
|
+
right_footer
|
|
374
|
+
Text for right footer
|
|
375
|
+
|
|
376
|
+
Raises
|
|
377
|
+
------
|
|
378
|
+
ValueError
|
|
379
|
+
Must specify at least one footer
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
None
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
if any((center_footer, left_footer, right_footer)):
|
|
387
|
+
_xl_sheet.set_footer(
|
|
388
|
+
"".join([
|
|
389
|
+
f"&L{left_footer}" if left_footer else "",
|
|
390
|
+
f"&C{center_footer}" if center_footer else "",
|
|
391
|
+
f"&R{right_footer}" if right_footer else "",
|
|
392
|
+
])
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
else:
|
|
396
|
+
raise ValueError("must specify at least one footer")
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def array_to_sheet(
|
|
400
|
+
_xl_book: Workbook,
|
|
401
|
+
_xl_sheet: Worksheet,
|
|
402
|
+
_data_table: Sequence[Any] | NDArray[Any],
|
|
403
|
+
_row_id: int,
|
|
404
|
+
_col_id: int = 0,
|
|
405
|
+
/,
|
|
406
|
+
*,
|
|
407
|
+
cell_format: Sequence[CFmt | Sequence[CFmt]] | CFmt | None = None,
|
|
408
|
+
green_bar_flag: bool = True,
|
|
409
|
+
ragged_flag: bool = True,
|
|
410
|
+
) -> tuple[int, int]:
|
|
411
|
+
"""
|
|
412
|
+
Write a 2-D array to a worksheet.
|
|
413
|
+
|
|
414
|
+
The given array is required be a two-dimensional array, whether
|
|
415
|
+
a nested list, nested tuple, or a 2-D numpy ndarray. The array is assumed
|
|
416
|
+
to be ragged by default, i.e. not all rows are the same length, and some
|
|
417
|
+
cells may contain lists, etc. For rectangular arrays, set `ragged_flag` to
|
|
418
|
+
false if you wish to provide a format tuple with distinct formats for each
|
|
419
|
+
column in the rectangular array.
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
Parameters
|
|
423
|
+
----------
|
|
424
|
+
_xl_book
|
|
425
|
+
Workbook object
|
|
426
|
+
|
|
427
|
+
_xl_sheet
|
|
428
|
+
Worksheet object to which to write the give array
|
|
429
|
+
|
|
430
|
+
_data_table
|
|
431
|
+
Array to be written
|
|
432
|
+
|
|
433
|
+
_row_id
|
|
434
|
+
Row number of top left corner of range to write to
|
|
435
|
+
|
|
436
|
+
_col_id
|
|
437
|
+
Column number of top left corner of range to write to
|
|
438
|
+
|
|
439
|
+
cell_format
|
|
440
|
+
Format specification for range to be written
|
|
441
|
+
|
|
442
|
+
green_bar_flag
|
|
443
|
+
Whether to highlight alternating rows as in green bar paper
|
|
444
|
+
|
|
445
|
+
ragged_flag
|
|
446
|
+
Whether to write ragged array, i.e. rows not all the same length
|
|
447
|
+
or not all cells are scalar-valued
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
Raises
|
|
451
|
+
------
|
|
452
|
+
ValueError
|
|
453
|
+
If array is not two-dimensional
|
|
454
|
+
|
|
455
|
+
ValueError
|
|
456
|
+
If ragged_flag is False and array is not rectangular
|
|
457
|
+
|
|
458
|
+
ValueError
|
|
459
|
+
If array is not rectangular and cell_format is a Sequence
|
|
460
|
+
|
|
461
|
+
ValueError
|
|
462
|
+
If array is rectangular but length of format tuple does not
|
|
463
|
+
match row-length
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
Tuple giving address of cell at right below and after range written
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
Notes
|
|
472
|
+
-----
|
|
473
|
+
|
|
474
|
+
The keyword argument cell_format may be passed a tuple of :class:`CFmt` enums,
|
|
475
|
+
if, and only if, ragged_flag is False. If cell_format is a tuple, it must
|
|
476
|
+
have length equal to the number of cells in each row of the passed array.
|
|
477
|
+
Further, members of cell_format must each be a :class:`CFmt` enum or a
|
|
478
|
+
tuple of :class:`CFmt` enums; in other words, :meth:`CFmt.ensure_cell_format_spec_tuple`
|
|
479
|
+
must return True for any tuple `_c` passed as `cell_format`.
|
|
480
|
+
|
|
481
|
+
"""
|
|
482
|
+
|
|
483
|
+
if not ragged_flag:
|
|
484
|
+
try:
|
|
485
|
+
if np.ndim(_data_table) != 2:
|
|
486
|
+
raise ValueError("Given array must be two-dimensional.")
|
|
487
|
+
except ValueError as _err:
|
|
488
|
+
raise ValueError(
|
|
489
|
+
"Given array must be rectangular and homogenous, with scalar members."
|
|
490
|
+
" Alternatively, try with ragged_flag=True."
|
|
491
|
+
)
|
|
492
|
+
raise _err
|
|
493
|
+
elif not (
|
|
494
|
+
isinstance(_data_table, Sequence | np.ndarray)
|
|
495
|
+
and isinstance(_data_table[0], Sequence | np.ndarray)
|
|
496
|
+
):
|
|
497
|
+
raise ValueError("Given array must be two-dimensional array.")
|
|
498
|
+
|
|
499
|
+
# Get the array dimensions and row and column numbers for Excel
|
|
500
|
+
_num_rows = len(_data_table)
|
|
501
|
+
_bottom_row_id = _row_id + _num_rows
|
|
502
|
+
_num_cols = len(_data_table[0])
|
|
503
|
+
_right_column_id = _col_id + _num_cols
|
|
504
|
+
|
|
505
|
+
_cell_format: Sequence[CFmt | Sequence[CFmt]]
|
|
506
|
+
if isinstance(cell_format, Sequence):
|
|
507
|
+
if _num_rows > 1 and ragged_flag:
|
|
508
|
+
raise ValueError(
|
|
509
|
+
"It is not clear whether the sequence of formats applies to all cells,"
|
|
510
|
+
" or to each cell respectively. Please provide a single-valued cell_format."
|
|
511
|
+
" Alternatively, you can iterate over the array using scalar_to_sheet()."
|
|
512
|
+
)
|
|
513
|
+
elif not len(cell_format) == len(_data_table[0]):
|
|
514
|
+
raise ValueError("Format tuple does not match data in length.")
|
|
515
|
+
CFmt.ensure_cell_format_spec_tuple(cell_format)
|
|
516
|
+
_cell_format = cell_format
|
|
517
|
+
elif isinstance(cell_format, CFmt):
|
|
518
|
+
_cell_format = (cell_format,) * len(_data_table[0])
|
|
519
|
+
else:
|
|
520
|
+
_cell_format = (CFmt.XL_DEFAULT,) * len(_data_table[0])
|
|
521
|
+
|
|
522
|
+
# construct vector of xlslwrter.format.Format objects
|
|
523
|
+
_wbk_formats = tuple(CFmt.xl_fmt(_xl_book, _cf) for _cf in _cell_format)
|
|
524
|
+
_wbk_formats_greened = _wbk_formats
|
|
525
|
+
if _num_rows > 1:
|
|
526
|
+
_wbk_formats_greened = (
|
|
527
|
+
tuple(
|
|
528
|
+
CFmt.xl_fmt(
|
|
529
|
+
_xl_book,
|
|
530
|
+
(*_cf, CFmt.BAR_FILL)
|
|
531
|
+
if isinstance(_cf, Sequence)
|
|
532
|
+
else (_cf, CFmt.BAR_FILL),
|
|
533
|
+
)
|
|
534
|
+
for _cf in _cell_format
|
|
535
|
+
)
|
|
536
|
+
if green_bar_flag
|
|
537
|
+
else _wbk_formats
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
for _ri, _rv in enumerate(_data_table):
|
|
541
|
+
_wbk_fmt_tuple = _wbk_formats_greened if _ri % 2 else _wbk_formats
|
|
542
|
+
for _ci, _cv in enumerate(_rv):
|
|
543
|
+
_cf = _wbk_fmt_tuple[_ci]
|
|
544
|
+
scalar_to_sheet(_xl_book, _xl_sheet, _row_id + _ri, _col_id + _ci, _cv, _cf)
|
|
545
|
+
|
|
546
|
+
_right_column_id = (
|
|
547
|
+
_col_id + len(_rv) if len(_rv) > _num_cols else _right_column_id
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
return _bottom_row_id, _right_column_id
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@overload
|
|
554
|
+
def scalar_to_sheet(
|
|
555
|
+
_xl_book: Workbook,
|
|
556
|
+
_xl_sheet: Worksheet,
|
|
557
|
+
_address0: str,
|
|
558
|
+
_value: Any,
|
|
559
|
+
_format: CFmt | Sequence[CFmt | Sequence[CFmt]] | None,
|
|
560
|
+
/,
|
|
561
|
+
) -> None: ...
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
@overload
|
|
565
|
+
def scalar_to_sheet(
|
|
566
|
+
_xl_book: Workbook,
|
|
567
|
+
_xl_sheet: Worksheet,
|
|
568
|
+
_address0: int,
|
|
569
|
+
_address1: int,
|
|
570
|
+
_value: Any,
|
|
571
|
+
_format: CFmt | Sequence[CFmt | Sequence[CFmt]] | None,
|
|
572
|
+
/,
|
|
573
|
+
) -> None: ...
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def scalar_to_sheet(
|
|
577
|
+
_xl_book: Workbook, _xl_sheet: Worksheet, /, *_s2s_args: Any
|
|
578
|
+
) -> None:
|
|
579
|
+
"""
|
|
580
|
+
Write to a single cell in a worksheet.
|
|
581
|
+
|
|
582
|
+
Parameters
|
|
583
|
+
----------
|
|
584
|
+
_xl_book
|
|
585
|
+
Workbook object for defining formats, and writing data
|
|
586
|
+
|
|
587
|
+
_xl_sheet
|
|
588
|
+
Worksheet object to which to write the given scalar
|
|
589
|
+
|
|
590
|
+
_cell_addr
|
|
591
|
+
An Excel cell address string in 'A1' format
|
|
592
|
+
|
|
593
|
+
_address0
|
|
594
|
+
Index-0 row number of destintaion cell
|
|
595
|
+
|
|
596
|
+
_address1
|
|
597
|
+
Index-0 column number of destintaion cell
|
|
598
|
+
|
|
599
|
+
_value
|
|
600
|
+
Value to write
|
|
601
|
+
|
|
602
|
+
_format
|
|
603
|
+
Member of :class:`CFmt`, or tuple thereof
|
|
604
|
+
|
|
605
|
+
Raises
|
|
606
|
+
------
|
|
607
|
+
ValueError
|
|
608
|
+
If too many or too few arguments
|
|
609
|
+
ValueError
|
|
610
|
+
If incorrect/incomplete specification for Excel cell data
|
|
611
|
+
|
|
612
|
+
Returns
|
|
613
|
+
-------
|
|
614
|
+
None
|
|
615
|
+
|
|
616
|
+
Notes
|
|
617
|
+
-----
|
|
618
|
+
For more information on xlsxwriter cell-address notation, see:
|
|
619
|
+
https://xlsxwriter.readthedocs.io/working_with_cell_notation.html
|
|
620
|
+
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
_address: tuple[str] | tuple[int, int]
|
|
624
|
+
_value: Any
|
|
625
|
+
_format: CFmt | Sequence[CFmt | Sequence[CFmt]] | None
|
|
626
|
+
|
|
627
|
+
if isinstance(_s2s_args[0], str):
|
|
628
|
+
if len(_s2s_args) not in (2, 3):
|
|
629
|
+
raise ValueError("Incorrect number of arguments.")
|
|
630
|
+
_address = (_s2s_args[0],)
|
|
631
|
+
_value = _s2s_args[1]
|
|
632
|
+
_format = _s2s_args[2] if len(_s2s_args) == 3 else None
|
|
633
|
+
elif isinstance(_s2s_args[0], int):
|
|
634
|
+
if not isinstance(_s2s_args[1], int) or len(_s2s_args) not in (3, 4):
|
|
635
|
+
print(repr(_s2s_args))
|
|
636
|
+
raise ValueError("Incorrect/incomplete specification for Excel cell data.")
|
|
637
|
+
_address = _s2s_args[:2]
|
|
638
|
+
_value = _s2s_args[2]
|
|
639
|
+
_format = _s2s_args[3] if len(_s2s_args) == 4 else None
|
|
640
|
+
else:
|
|
641
|
+
raise ValueError("Incorrect/incomplete specification for Excel cell data.")
|
|
642
|
+
|
|
643
|
+
_write_args = (
|
|
644
|
+
*_address,
|
|
645
|
+
(
|
|
646
|
+
repr(_value)
|
|
647
|
+
if np.ndim(_value) or _value in (np.inf, -np.inf, np.nan)
|
|
648
|
+
else _value
|
|
649
|
+
),
|
|
650
|
+
)
|
|
651
|
+
_write_args += (CFmt.xl_fmt(_xl_book, _format),) if _format else ()
|
|
652
|
+
|
|
653
|
+
if _value is None or _value == "":
|
|
654
|
+
_xl_sheet.write_blank(*_write_args)
|
|
655
|
+
elif (
|
|
656
|
+
isinstance(_value, str)
|
|
657
|
+
or np.ndim(_value)
|
|
658
|
+
or _value in (np.inf, -np.inf, np.nan)
|
|
659
|
+
):
|
|
660
|
+
_xl_sheet.write_string(*_write_args)
|
|
661
|
+
else:
|
|
662
|
+
_xl_sheet.write(*_write_args)
|