# External
from typing import Any
# Internal main
from excelbird._layout_references import Globals
from excelbird._base.dotdict import Style
from excelbird.styles import default_table_style
from excelbird._utils.util import (
get_idx,
)
from excelbird._utils.argument_parsing import (
combine_args_and_children_to_list,
move_remaining_kwargs_to_dict,
)
from excelbird._utils.pass_attributes import (
pass_dict_to_children,
)
# Internal core
from excelbird.core.gap import Gap
from excelbird.core.cell import Cell
from excelbird.core.series import Col, Row
from excelbird.core.frame import Frame, VFrame
from excelbird.core.stack import VStack, Stack
from excelbird.core.expression import Expr
from excelbird.core.function import Func
[docs]class Sheet(VStack):
"""
Behaves similar to :class:`VStack` - it can hold any element, and arranges its children vertically - but
lacks some styling features like margin and padding.
* Direction: **vertical**
* Child Type: :class:`Stack`, :class:`VStack`, :class:`Frame`, :class:`VFrame`, :class:`Col`, :class:`Row`, :class:`Cell`
.. note:: If the first argument in ``*args`` is a string, it will be used as the ``title`` attribute. This allows for better readability in complex layouts with multiple sheets, so a sheet's title can be visible at the start of the container.
Parameters
----------
*args : Union[Stack, VStack, Frame, VFrame, Col, Row, Cell, list, tuple, str, int, float, pd.Series, pd.DataFrame, np.ndarray, Gap, Expr, Func, set, None]
Can take any layout element (besides `Book` and `Sheet`) or any value that can
be used to construct a layout element.
children : list, optional
Will be combined with args
title : str, optional
Sheet name
sep : Gap or bool or int or dict, optional
A sep in any excelbird layout element inserts a Gap between each of its children.
If True, a default of Gap(1) is used. If int, Gap(sep) will be used. If a dict,
``Gap(1, **sep)`` will be used.
tab_color : str, optional
Hex color for tab color.
end_gap : bool or int or dict or Gap, optional
Applies a Gap to cells below and to the right of the Sheet. The Gap determines
the number of columns filled, and 1/3 the number of rows filled. The default
is Gap(35, fill_color="FFFFFF") (white). This means apply whitespace (hide grid)
for 35 columns, and 105 rows surrounding the Sheet contents.
isolate : bool, optional
After initialization, clear the global memory of ids and headers, so references
in future declared Sheets won't conflict with previous ones. This will also isolate
previously declared Sheets, so they musn't reference elements declared after the current
one.
hidden : bool, optional
Whether to hide the Sheet
zoom : int, optional
Percentage zoom level. (Passing None or 100 will have the same effect)
background_color : str, optional
Hex code for background_color. Will be applied to fill_color of any Gap child who hasn't
specified its own fill_color, and to any child Stack/VStack's margins. Will also be passed
down to any child (Cell excluded) who hasn't specified its own background_color.
cell_style : dict, optional
Applied to each child who has cell_style
header_style : dict, optional
Applied to each child who has header_style
table_style : dict or bool, optional
Applied to each child who has table_style
**kwargs : Any
Remaining kwargs will be applied to self.cell_style
"""
_dimensions = -1
def __init__(
self,
*args: Any,
children: list | None = None,
title: str | None = None,
sep: Any | None = None,
tab_color: str | None = None,
end_gap: bool | int | dict | Gap | None = None,
isolate: bool | None = None,
hidden: bool | None = None,
zoom: int | None = None,
background_color: str | None = None,
cell_style: Style | dict | None = None,
header_style: Style | dict | None = None,
table_style: Style | dict | bool | None = None,
**kwargs,
) -> None:
children = combine_args_and_children_to_list(args, children)
first_arg = get_idx(children, 0)
if isinstance(first_arg, str):
title = children.pop(0)
children = [i for i in children if i is not None]
# Alternative to init_from_same_dimension_type
if len(children) == 1 and isinstance(first_arg, Sheet):
children = list(first_arg)
new_kwargs = first_arg.__dict__
new_kwargs.pop("_loc")
for key, val in new_kwargs.items():
setattr(self, key, val)
if cell_style is None:
cell_style = dict()
if header_style is None:
header_style = dict()
if table_style is None or table_style is False:
table_style = dict()
elif table_style is True:
table_style = default_table_style
self._format_args(children)
move_remaining_kwargs_to_dict(kwargs, cell_style)
self._loc = None
self.title = title
self.tab_color = tab_color
self.end_gap = end_gap
self.isolate = isolate
self.hidden = hidden
self.zoom = zoom
self.background_color = background_color
# Dicts that must be passed to children
self.cell_style = Style(**cell_style)
self.header_style = Style(**header_style)
self.table_style = Style(**table_style)
self._init(children)
if sep is not None:
self._insert_separator(sep)
if self.isolate is True:
self._resolve_all_references()
self._resolve_all_references()
self._resolve_all_references()
# raise ExpressionResolutionError(
# "Couldn't resolve all expression references.\nThis error was raised during the "
# f"creation of sheet, '{self.title}'.\nWhen `isolate=True` for "
# "a sheet, it will try to resolve all of its expression references "
# "immediately after being created, and then clear the global memory of references "
# "so any element created afterwards won't be able to reference them."
# )
Globals.clear_references()
def _resolve_all_references(self) -> bool:
Expr._set_use_ref_for_container_recursive(self)
all_resolved = False
attempts = 0
while not all_resolved and attempts <= 5:
all_resolved = True
attempts += 1
if Expr._resolve_container_recursive(self) is False:
all_resolved = False
if Func._resolve_container_recursive(self) is False:
all_resolved = False
return all_resolved
def _apply_end_gap(self) -> None:
gap = self.end_gap
if gap is None or gap is False:
return
if (
not type(gap) in [bool, int]
and not isinstance(gap, Gap)
and not isinstance(gap, dict)
):
raise ValueError("end_gap must be bool, int, Gap, dict")
default_size = 35
# default_color = "FFFFFF" # white
if gap is True:
gap = Gap(default_size)
elif type(gap) == int:
gap = Gap(gap)
elif isinstance(gap, dict):
if "size" in gap:
gap = Gap(gap.pop("size"), **gap)
else:
gap = Gap(default_size, **gap)
if "fill_color" not in gap.kwargs and self.background_color is not None:
gap.kwargs["fill_color"] = self.background_color
if "row_multiplier" not in gap.kwargs:
gap.kwargs["row_multiplier"] = 3
size, kwargs = int(gap), gap.kwargs
row_multiplier = kwargs.pop("row_multiplier")
width, height = self.width, self.height
new_elements = []
for i, elem in reversed(list(enumerate(self))):
new_elements.insert(0, self.pop(i))
new_cols = size
new_rows = size * row_multiplier
full_height = height + new_rows
self.append(
Stack(
VStack(
*new_elements,
VFrame(
*[
Row(*[Cell("", **kwargs) for _ in range(width)])
for _ in range(new_rows)
]
),
),
Frame(
*[
Col(*[Cell("", **kwargs) for _ in range(full_height)])
for _ in range(new_cols)
]
),
)
)
def _resolve_gaps(self) -> None:
super()._resolve_gaps()
self._apply_end_gap()
def _write(self) -> None:
if self.tab_color is not None:
self._loc.ws.sheet_properties.tabColor = self.tab_color
if self.hidden is True:
self._loc.ws.sheet_state = "hidden"
if self.zoom is not None:
self._loc.ws.sheet_view.zoomScale = self.zoom
pass_dict_to_children(self, "cell_style")
pass_dict_to_children(self, "header_style")
pass_dict_to_children(self, "table_style")
for elem in self:
elem._write()
if self.isolate is True:
Globals.clear_references(self._loc.ws.title)