{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Styling" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "This is one of the greatest advantages of using a tree-like layout design instead of scripting.\n", "If you have experience in HTML/CSS the following examples will feel intuitive. Here are the rules:\n", "\n", "1. A container's styling will be passed down to each of its children\n", "2. An element will **always** override its parent for each style attribute declared directly.\n", "3. Exceptions to rule #1 are made when the styling is *spatial* in nature. For instance, ``border`` will always be applied to the perimeter of the element it was declared on." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import excelbird as xb\n", "from excelbird import *\n", "PATH = \"test.xlsx\"" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## First, a fun example\n", "\n", "Here's a reconstruction of Excel's theme color grid.\n", "\n", "At the end of this lesson, you'll create this table in **one line of code**.\n", "\n", "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Styling an Excelbird Workbook\n", "\n", "Before we continue, let's set up some default settings to apply to our Book in the following examples.\n", "\n", "Each argument will be passed down to the children, *unless* it's accepted by an element. For instance, `auto_open` will apply to the outer Book, `zoom` and `end_gap` will be applied to *each* Sheet, and the remaining settings will be applied to all Cells. Read more about these parameters in the respective element's documentation" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "book_settings = dict(\n", " row_height=29,\n", " col_width=5, # same dimensions as rows. Square cells.\n", " center=True, # sets align_x='center' and align_y='center'\n", " bold=True,\n", " size=14, # font\n", " auto_open=True, # opens our book for us\n", " zoom=350,\n", " end_gap=True,\n", " # Note: end_gap surrounds the sheet's contents with Cells.\n", " # (True applies a default). This way the row_height\n", " # and col_width are applied everywhere\n", ")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Before we start, let's take a look at what these settings are doing. Notice: because we set `end_gap=True`, our custom column width and row height has been applied to all visible cells on our Sheet - not just the Frame" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Frame(\n", " Col(1, 2, 3),\n", " Col(4, 5, 6),\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "`fill_color` is another Cell attribute. Like above, we can pass it to any parent container to affect all cells" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1, 2, 3),\n", " Col(4, 5, 6),\n", " ),\n", " fill_color=xb.colors.theme.green2,\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The child's spec will always override its parent" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "theme = xb.colors.theme\n", "# Note: green4 is darker, green1 is lighter\n", "Book(\n", " VStack(\n", " Frame(\n", " Col(\n", " Cell(1, fill_color=theme.green4), # Cell-level\n", " Cell(2),\n", " fill_color=theme.green3, # Col-level\n", " size=10,\n", " ),\n", " Col(3, 4),\n", " fill_color=theme.green2, # Frame-level\n", " ),\n", " Row(10, 20),\n", " fill_color=theme.green1, # VStack-level\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Colors\n", "\n", "For the next few examples I'll use ``xb.colors.theme.groups``, each color in 'groups' contains a list of 6 shades, as seen in the default color picker you use every time you open Excel." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "reds = theme.groups.red[1:] # last 5 colors.\n", "blues = theme.groups.light_blue[1:]\n", "Book(\n", " Sheet(\n", " Row([Cell(i+1, fill_color=c) for i,c in enumerate(reds)]),\n", " Row([Cell(i+1, fill_color=c) for i,c in enumerate(blues)]),\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Cell attribute, ``auto_color_font`` ensures the text is always readable" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Sheet(\n", " Row([Cell(i+1, fill_color=c) for i,c in enumerate(reds)]),\n", " Row([Cell(i+1, fill_color=c) for i,c in enumerate(blues)]),\n", " auto_color_font=True,\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "It gets better: ``auto_shade_font`` will take your background color and give the font a *scaled* version of it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Sheet(\n", " Row([Cell(i+1, fill_color=c) for i,c in enumerate(reds)]),\n", " Row([Cell(i+1, fill_color=c) for i,c in enumerate(blues)]),\n", " auto_shade_font=True,\n", " ),\n", " **book_settings,\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Styling Headers\n", "\n", "The series `header` attribute is styled separately from other children" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "book_settings = dict(\n", " row_height=29,\n", " col_width=5,\n", " center=True,\n", " auto_open=True,\n", " zoom=350,\n", " end_gap=True,\n", ")" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Notice the Row's header is completely unaffected by the rest of our styling" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Row(1,2,3, header=\"one\", fill_color=theme.purple1),\n", " **book_settings,\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We must use the `header_style` attribute" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Row(\n", " Cell(1),\n", " Cell(2),\n", " Cell(3),\n", " header=\"one\",\n", " fill_color=theme.purple1,\n", " header_style=dict(\n", " center=True,\n", " fill_color=theme.purple4,\n", " bold=True,\n", " auto_color_font=True,\n", " )\n", " ),\n", " **book_settings,\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Borders\n", "\n", "Border is a spatial styling attribute, so it affects frames, series, and cells differently, rather than being passed down to the cell level.\n", "\n", "Currently, border is only available for Frame/VFrame, Col/Row, and Cell.\n", "\n", "If you want to specify border at the parent-level but apply it to each child cell *individually*, all layout elements have a `cell_style` attribute where you can specify border." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "book_settings = dict(\n", " row_height=29,\n", " col_width=5,\n", " center=True,\n", " bold=True,\n", " size=14,\n", " auto_open=True,\n", " zoom=350,\n", " end_gap=True,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1,2,3),\n", " Col(4,5,6),\n", " border=True,\n", " ),\n", " margin=1, # So top and left borders aren't hidden\n", " ),\n", " **book_settings,\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Or we can use `cell_style` to apply border to each child cell individually" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1,2,3),\n", " Col(4,5,6),\n", " cell_style=dict(border=True),\n", " ),\n", " margin=1, # So top and left borders aren't hidden\n", " ),\n", " **book_settings,\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "### Border Syntax\n", "\n", "Border syntax is **the same** for any element that accepts it.\n", "\n", "Syntax closely resembles the behavior of borders in CSS.\n", "\n", "The explanation below will focus on inline shorthand (customizing all sides at once with just the `border` attribute),\n", "but individual sides can be applied conveniently with `border_top`, `border_right`, etc., with the same rules as below.\n", "\n", "#### Sides\n", "\n", "We can customize all 4 sides at once, in the order **top, right, bottom, left**\n", "\n", "`border=True` is really interpreted as `border=[True, True, True, True]`\n", "\n", "A list with fewer than 4 elements will be reflected:\n", "\n", "- `[True, False]` -> `[True, False, True, False]`\n", "- `['thick', 'medium', 'thin']` -> `['thick', 'medium', 'thin', 'medium']`\n", "\n", "#### Style\n", "\n", "The style of a border is specified in a *tuple*: `(, )`\n", "\n", "For each side, `True` will apply the default `('thin', '000000')`\n", "\n", "If only a string is passed instead of a tuple or boolean, excelbird will interpret it and figure out whether the value represents a weight or a color. This is deterministic, since there's only a limited selection of valid weights, and none of them are valid hex codes\n", "\n", "- `'thick'` -> `('thick', True)`\n", "- `'D5D5D5'` -> `(True, 'D5D5D5')`\n", "\n", "We can combine everything together to fully describe the border in one line:\n", "\n", "```python\n", "# Apply thick black border to top, dashDotted dark blue\n", "# border to right side, and default to left and bottom\n", "border=['thick', ('dashDot', '4F81BD'), True, True]\n", "```" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we'll apply:\n", "\n", "- Top: Thick, default color\n", "- Right: Medium Dashed, blue\n", "- Bottom: default\n", "- Left: Medium Dashed, blue" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "blue = xb.colors.theme.light_blue\n", "Book(\n", " Stack(\n", " Cell(1, border=['thick', ('mediumDashed', blue), True]),\n", " margin=1,\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We can set individual sides more easily with `border_top`, `border_right`, etc." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Cell(1, border_right=('mediumDashed', blue)),\n", " margin=1\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "You're probably wondering..." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('dashDot',\n", " 'dashDotDot',\n", " 'dashed',\n", " 'dotted',\n", " 'double',\n", " 'hair',\n", " 'medium',\n", " 'thick',\n", " 'thin',\n", " 'mediumDashDot',\n", " 'mediumDashDotDot',\n", " 'mediumDashed',\n", " 'slantDashDot')" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "xb.HasBorder.valid_weights" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Margin/Padding\n", "\n", ".. warning::\n", " This feature is still in development. Expect the behavior of these attributes to change at any time. Therefore, the explanations/examples below will be brief until design is finalized" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "import excelbird as xb\n", "from excelbird import *\n", "PATH = \"test.xlsx\"" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Margin and padding is available for Stack and VStack only.\n", "\n", "- **Margin**: Applies empty space around an element, and is unaffected by the element's style, and instead inherits the parent's style.\n", "- **Padding**: Applies empty space around the element (inside of margin) and inherits the element's `background_color`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1,2),\n", " Col(3,4),\n", " ),\n", " margin=1,\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "**Padding**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1,2),\n", " Col(3,4),\n", " ),\n", " padding=1,\n", " background_color=theme.light_blue2\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "**Margin and padding**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1,2),\n", " Col(3,4),\n", " ),\n", " margin=1,\n", " padding=1,\n", " background_color=theme.light_blue2\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "**Individual sides - padding**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Stack(\n", " Frame(\n", " Col(1,2),\n", " Col(3,4),\n", " ),\n", " margin=1,\n", " padding_right=1,\n", " padding_bottom=1,\n", " background_color=theme.light_blue2\n", " ),\n", " **book_settings\n", ").write(PATH)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Excel Theme Color Grid\n", "\n", "We're going to add a twist: Create not only the grid, but also a transposed version of it that **uses cell references** and keeps all of its styling *except* borders.\n", "\n", "The entire Book layout must be a single line of code.\n", "\n", "Here's the end result we're working towards:" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ ".. note::\n", " This lesson is still in the process of being written. Come back soon for a full explanation. Until then, here is the solution code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Book(\n", " Sheet(\n", " Frame(\n", " [Col(\n", " [Cell(f\"{color}{i}\", fill_color=shades[i]) for i in range(1, len(shades))],\n", " header=color.upper(),\n", " border=['thick', ('mediumDashed', 'FFFFFF')],\n", " header_style=dict(fill_color=shades[0]),\n", " ) for color, shades in xb.colors.theme_groups.items()],\n", " id=\"main\"\n", " ),\n", " Gap(1, fill_color=\"FFFFFF\"),\n", " Cell(\"Tranposed\", row_height=30, size=16, align_y='center', bold=True, border_bottom='mediumDashed', indent=8, merge=(0,9)),\n", " Gap(1, fill_color=\"FFFFFF\"),\n", " {\"[main].ref(inherit_style=True).transpose().set(border=False)\"},\n", " ),\n", " row_height=18,\n", " center=True,\n", " col_width=11,\n", " auto_shade_font=True,\n", " header_style=dict(\n", " bold=True,\n", " row_height=26,\n", " center=True,\n", " auto_shade_font=True,\n", " border_left=('thick', 'FFFFFF'),\n", " ),\n", ").write(PATH)" ] } ], "metadata": { "kernelspec": { "display_name": "env", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.1" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "6ee8c39a5e4f5a5ffe92ac0abdca1f10885ded7e28c74450614be57680931831" } } }, "nbformat": 4, "nbformat_minor": 2 }