{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [Layout and Styling of Jupyter widgets](06.00-Layout-and-Styling-Overview.ipynb) | [Contents](00.00-index.ipynb) | [OPTIONAL - Widget label styling](06.02-OPTIONAL-widget-label-styling.ipynb) >" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# More about the flexbox layout\n", "\n", "The flexbox layout enables you to make extensive changes to the appearance of a widget by making changes to its layout.\n", "\n", "### Acknowledgement\n", "\n", "The following material on the flexbox layout follows the lines of the article [A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) by Chris Coyier, and uses text and various images from the article [with permission](https://css-tricks.com/license/).\n", "\n", "## Basics and terminology\n", "\n", "The flexbox layout spec is excellent for laying out items in a single direction, either horizontally or vertically.\n", "\n", "Since flexbox is a whole module and not a single property, it involves a lot of things including its whole set of properties. Some of them are meant to be set on the container (parent element, known as \"flex container\") whereas the others are meant to be set on the children (known as \"flex items\").\n", "If regular layout is based on both block and inline flow directions, the flex layout is based on \"flex-flow directions\". Please have a look at this figure from the specification, explaining the main idea behind the flex layout.\n", "\n", "![Flexbox](./images/flexbox.png)\n", "\n", "Basically, items will be laid out following either the `main axis` (from `main-start` to `main-end`) or the `cross axis` (from `cross-start` to `cross-end`).\n", "\n", "- `main axis` - The main axis of a flex container is the primary axis along which flex items are laid out. Beware, it is not necessarily horizontal; it depends on the flex-direction property (see below).\n", "- `main-start | main-end` - The flex items are placed within the container starting from main-start and going to main-end.\n", "- `main size` - A flex item's width or height, whichever is in the main dimension, is the item's main size. The flex item's main size property is either the ‘width’ or ‘height’ property, whichever is in the main dimension.\n", "cross axis - The axis perpendicular to the main axis is called the cross axis. Its direction depends on the main axis direction.\n", "- `cross-start | cross-end` - Flex lines are filled with items and placed into the container starting on the cross-start side of the flex container and going toward the cross-end side.\n", "- `cross size` - The width or height of a flex item, whichever is in the cross dimension, is the item's cross size. The cross size property is whichever of ‘width’ or ‘height’ that is in the cross dimension.\n", "\n", "### The VBox and HBox helpers\n", "\n", "The `VBox` and `HBox` helper classes provide simple defaults to arrange child widgets in vertical and horizontal boxes. They are roughly equivalent to:\n", "\n", "```Python\n", "def VBox(*pargs, **kwargs):\n", " \"\"\"Displays multiple widgets vertically using the flexible box model.\"\"\"\n", " box = Box(*pargs, **kwargs)\n", " box.layout.display = 'flex'\n", " box.layout.flex_flow = 'column'\n", " box.layout.align_items = 'stretch'\n", " return box\n", "\n", "def HBox(*pargs, **kwargs):\n", " \"\"\"Displays multiple widgets horizontally using the flexible box model.\"\"\"\n", " box = Box(*pargs, **kwargs)\n", " box.layout.display = 'flex'\n", " box.layout.align_items = 'stretch'\n", " return box\n", "```\n", "\n", "Note in particular that it is the `flex_flow` that controls whether box children are arranged in a row (for `HBox`) or a column (for `VBox`).\n", "\n", "### Examples\n", "\n", "#### Three buttons in an HBox, items flex proportionally to their weight" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Layout, Button, HBox\n", "\n", "items = [\n", " Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),\n", " Button(description='weight=3; 0%', layout=Layout(flex='3 1 0%', width='auto'), button_style='danger'),\n", " Button(description='weight=1; 0%', layout=Layout(flex='1 1 0%', width='auto'), button_style='danger'),\n", "]\n", "box_layout = Layout(align_items='stretch', width='70%')\n", "HBox(children=items, layout=box_layout)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Couple of different ways to position items in the container" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Button, HBox, VBox\n", "left_box = VBox([Button(description=\"left {}\".format(i)) for i in range(1,3)])\n", "right_box = VBox([Button(description=\"right {}\".format(i)) for i in range(1,3)])\n", "HBox([left_box, right_box])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "left_box.children[0].style.button_color = 'lightblue'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "right_box.children = [Button(description=\"new right {}\".format(i)) for i in range(1,5)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "right_box.layout.flex_flow = 'column-reverse'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "left_box.layout.justify_content = 'flex-end'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "left_box.layout.justify_content = 'space-between'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "left_box.children[0].layout.flex = '1'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "left_box.children[1].layout.flex = '1'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A toolbar with three buttons and a status indicator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Valid\n", "tools = [Button(description=\"Tool {}\".format(i)) for i in range(1,4)]\n", "status = Valid(value=True, description='Status')\n", "HBox(tools + [status])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Align status to right*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "toolbar = HBox([HBox(tools), status], layout={'justify_content': 'space-between'})\n", "toolbar" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### A carousel" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Layout, Button, Box, Label, VBox\n", "\n", "item_layout = Layout(height='100px', min_width='40px')\n", "items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]\n", "box_layout = Layout(overflow='scroll hidden',\n", " border='3px solid black',\n", " width='500px',\n", " height='',\n", " flex_flow='row',\n", " display='flex')\n", "carousel = Box(children=items, layout=box_layout)\n", "VBox([Label('Scroll horizontally:'), carousel])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A widget for exploring layout options\n", "\n", "Use the dropdowns and sliders in the widget to change the layout of the box containing the colored buttons. Many of the CSS layout options described above are available, and the Python code to generate a `Layout` object reflecting the settings is in a `TextArea` in the widget.\n", "\n", "A few questions to answer after the demonstration of this (see the [detailed flexbox guide for a longer discussion](reference_guides/guide-flex-box.ipynb)):\n", "\n", "1. What does changing `justify_content` affect? You may find it easier to answer this if you set `wrap` to `wrap`.\n", "2. What does `align_items` affect?\n", "3. How is `align_content` different than `align_items`?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from layout_preview import layout\n", "layout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercises" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Four buttons in a box revisited: Change order and orientation\n", "\n", "This example, from earlier in this notebook, lays out 4 buttons vertically." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Layout, Button, Box\n", "\n", "items_layout = Layout(width='auto') # override the default width of the button to 'auto' to let the button grow\n", "\n", "box_layout = Layout(display='flex',\n", " flex_flow='column',\n", " align_items='stretch',\n", " border='solid',\n", " width='20%')\n", "\n", "words = ['correct', 'horse', 'battery', 'staple']\n", "items = [Button(description=word, layout=items_layout, button_style='danger') for word in words]\n", "box = Box(children=items, layout=box_layout)\n", "box" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Flexbox allows you to change the order and orientation of the children items in the flexbox without changing the children themselves.\n", "\n", "1. Change the `flex_flow` so that the buttons are displayed in a single column in *reverse order*.\n", "2. Change the `flex_flow` so that the buttons are displayed in a single *row* instead of a column.\n", "3. Try setting a few values of `align_items` and describe how it affects the display of the buttons.\n", "4. Make the box narrower by changing the `width`, then change `flex_flow` to lay out the buttons in rows that wrap so that there is a 2x2 grid of buttons.\n", "\n", "Feel free to figure out the layout using the tool above and copy/paste the layout here!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Carousel revisited: item layout\n", "\n", "The code that generated the carousel is reproduced below. Run the cell, then continue reading." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ipywidgets import Layout, Button, VBox, Label\n", "\n", "item_layout = Layout(height='100px', min_width='40px')\n", "items = [Button(layout=item_layout, description=str(i), button_style='warning') for i in range(40)]\n", "box_layout = Layout(overflow_x='scroll',\n", " border='3px solid black',\n", " width='500px',\n", " height='',\n", " flex_flow='row',\n", " display='flex')\n", "carousel = Box(children=items, layout=box_layout)\n", "VBox([Label('Scroll horizontally:'), carousel])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**To do:**\n", "\n", "1. Change the `min_width` for *one* of the `items`, say the first one. Does it affect only the first one, or all of them? Why?\n", "2. Change the `height` of *only* the first button. *Hint:* It needs its own `Layout`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "items[0].layout.min_width = 'FILL IN WITH A WIDTH'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "< [Layout and Styling of Jupyter widgets](06.00-Layout-and-Styling-Overview.ipynb) | [Contents](00.00-index.ipynb) | [OPTIONAL - Widget label styling](06.02-OPTIONAL-widget-label-styling.ipynb) >" ] } ], "metadata": { "jupytext": { "formats": "ipynb,md:myst" }, "kernelspec": { "display_name": "widgets-tutorial", "language": "python", "name": "widgets-tutorial" }, "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.8.10" } }, "nbformat": 4, "nbformat_minor": 4 }