Spaces:
Running
on
Zero
Running
on
Zero
#----------------------------------------------------------------------------- | |
# Copyright (c) 2012 - 2022, Anaconda, Inc., and Bokeh Contributors. | |
# All rights reserved. | |
# | |
# The full license is in the file LICENSE.txt, distributed with this software. | |
#----------------------------------------------------------------------------- | |
''' Functions for arranging bokeh layout objects. | |
''' | |
#----------------------------------------------------------------------------- | |
# Boilerplate | |
#----------------------------------------------------------------------------- | |
from __future__ import annotations | |
import logging # isort:skip | |
log = logging.getLogger(__name__) | |
#----------------------------------------------------------------------------- | |
# Imports | |
#----------------------------------------------------------------------------- | |
# Standard library imports | |
import math | |
from typing import ( | |
TYPE_CHECKING, | |
Any, | |
Dict, | |
Iterable, | |
Iterator, | |
List, | |
Sequence, | |
Tuple, | |
TypeVar, | |
cast, | |
overload, | |
) | |
# Bokeh imports | |
from .core.enums import Location, LocationType, SizingModeType | |
from .models import ( | |
Box, | |
Column, | |
GridBox, | |
LayoutDOM, | |
Plot, | |
ProxyToolbar, | |
Row, | |
Spacer, | |
ToolbarBox, | |
WidgetBox, | |
) | |
from .util.dataclasses import dataclass | |
from .util.deprecation import deprecated | |
if TYPE_CHECKING: | |
from .models import Toolbar, Widget | |
#----------------------------------------------------------------------------- | |
# Globals and constants | |
#----------------------------------------------------------------------------- | |
__all__ = ( | |
'column', | |
'grid', | |
'gridplot', | |
'GridSpec', | |
'layout', | |
'row', | |
'Spacer', | |
'widgetbox', | |
) | |
#----------------------------------------------------------------------------- | |
# General API | |
#----------------------------------------------------------------------------- | |
def row(children: List[LayoutDOM], *, sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Row: ... | |
def row(*children: LayoutDOM, sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Row: ... | |
def row(*children: LayoutDOM | List[LayoutDOM], sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Row: | |
""" Create a row of Bokeh Layout objects. Forces all objects to | |
have the same sizing_mode, which is required for complex layouts to work. | |
Args: | |
children (list of :class:`~bokeh.models.LayoutDOM` ): A list of instances for | |
the row. Can be any of the following - |Plot|, | |
:class:`~bokeh.models.Widget`, | |
:class:`~bokeh.models.Row`, | |
:class:`~bokeh.models.Column`, | |
:class:`~bokeh.models.ToolbarBox`, | |
:class:`~bokeh.models.Spacer`. | |
sizing_mode (``"fixed"``, ``"stretch_both"``, ``"scale_width"``, ``"scale_height"``, ``"scale_both"`` ): How | |
will the items in the layout resize to fill the available space. | |
Default is ``"fixed"``. For more information on the different | |
modes see :attr:`~bokeh.models.LayoutDOM.sizing_mode` | |
description on :class:`~bokeh.models.LayoutDOM`. | |
Returns: | |
Row: A row of LayoutDOM objects all with the same sizing_mode. | |
Examples: | |
>>> row(plot1, plot2) | |
>>> row(children=[widgets, plot], sizing_mode='stretch_both') | |
""" | |
_children = _parse_children_arg(*children, children=kwargs.pop("children", None)) | |
_handle_child_sizing(_children, sizing_mode, widget="row") | |
return Row(children=_children, sizing_mode=sizing_mode, **kwargs) | |
def column(children: List[LayoutDOM], *, sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Column: ... | |
def column(*children: LayoutDOM, sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Column: ... | |
def column(*children: LayoutDOM | List[LayoutDOM], sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Column: | |
""" Create a column of Bokeh Layout objects. Forces all objects to | |
have the same sizing_mode, which is required for complex layouts to work. | |
Args: | |
children (list of :class:`~bokeh.models.LayoutDOM` ): A list of instances for | |
the column. Can be any of the following - |Plot|, | |
:class:`~bokeh.models.Widget`, | |
:class:`~bokeh.models.Row`, | |
:class:`~bokeh.models.Column`, | |
:class:`~bokeh.models.ToolbarBox`, | |
:class:`~bokeh.models.Spacer`. | |
sizing_mode (``"fixed"``, ``"stretch_both"``, ``"scale_width"``, ``"scale_height"``, ``"scale_both"`` ): How | |
will the items in the layout resize to fill the available space. | |
Default is ``"fixed"``. For more information on the different | |
modes see :attr:`~bokeh.models.LayoutDOM.sizing_mode` | |
description on :class:`~bokeh.models.LayoutDOM`. | |
Returns: | |
Column: A column of LayoutDOM objects all with the same sizing_mode. | |
Examples: | |
>>> column(plot1, plot2) | |
>>> column(children=[widgets, plot], sizing_mode='stretch_both') | |
""" | |
_children = _parse_children_arg(*children, children=kwargs.pop("children", None)) | |
_handle_child_sizing(_children, sizing_mode, widget="column") | |
return Column(children=_children, sizing_mode=sizing_mode, **kwargs) | |
def widgetbox(*args: Widget, children: List[Widget] | None = None, sizing_mode: SizingModeType | None = None, **kwargs: Any) -> WidgetBox: | |
""" Create a column of bokeh widgets with predefined styling. | |
Args: | |
children (list of :class:`~bokeh.models.Widget`): A list of widgets. | |
sizing_mode (``"fixed"``, ``"stretch_both"``, ``"scale_width"``, ``"scale_height"``, ``"scale_both"`` ): How | |
will the items in the layout resize to fill the available space. | |
Default is ``"fixed"``. For more information on the different | |
modes see :attr:`~bokeh.models.LayoutDOM.sizing_mode` | |
description on :class:`~bokeh.models.LayoutDOM`. | |
Returns: | |
WidgetBox: A column layout of widget instances all with the same ``sizing_mode``. | |
Examples: | |
>>> widgetbox([button, select]) | |
>>> widgetbox(children=[slider], sizing_mode='scale_width') | |
""" | |
_children = _parse_children_arg(*args, children=children) | |
_handle_child_sizing(_children, sizing_mode, widget="widget box") | |
return WidgetBox(children=_children, sizing_mode=sizing_mode, **kwargs) | |
def layout(*args: LayoutDOM, children: List[LayoutDOM] | None = None, sizing_mode: SizingModeType | None = None, **kwargs: Any) -> Column: | |
""" Create a grid-based arrangement of Bokeh Layout objects. | |
Args: | |
children (list of lists of :class:`~bokeh.models.LayoutDOM` ): A list of lists of instances | |
for a grid layout. Can be any of the following - |Plot|, | |
:class:`~bokeh.models.Widget`, | |
:class:`~bokeh.models.Row`, | |
:class:`~bokeh.models.Column`, | |
:class:`~bokeh.models.ToolbarBox`, | |
:class:`~bokeh.models.Spacer`. | |
sizing_mode (``"fixed"``, ``"stretch_both"``, ``"scale_width"``, ``"scale_height"``, ``"scale_both"`` ): How | |
will the items in the layout resize to fill the available space. | |
Default is ``"fixed"``. For more information on the different | |
modes see :attr:`~bokeh.models.LayoutDOM.sizing_mode` | |
description on :class:`~bokeh.models.LayoutDOM`. | |
Returns: | |
Column: A column of ``Row`` layouts of the children, all with the same sizing_mode. | |
Examples: | |
>>> layout([[plot_1, plot_2], [plot_3, plot_4]]) | |
>>> layout( | |
children=[ | |
[widget_1, plot_1], | |
[slider], | |
[widget_2, plot_2, plot_3] | |
], | |
sizing_mode='fixed', | |
) | |
""" | |
_children = _parse_children_arg(*args, children=children) | |
return _create_grid(_children, sizing_mode, **kwargs) | |
def gridplot( | |
children: List[List[LayoutDOM | None]] | GridSpec, *, | |
sizing_mode: SizingModeType | None = None, | |
toolbar_location: LocationType | None = "above", | |
ncols: int | None = None, | |
width: int | None = None, | |
height: int | None = None, | |
plot_width: int | None = None, | |
plot_height: int | None = None, | |
toolbar_options: Any = None, # TODO | |
merge_tools: bool = True) -> LayoutDOM: | |
''' Create a grid of plots rendered on separate canvases. | |
The ``gridplot`` function builds a single toolbar for all the plots in the | |
grid. ``gridplot`` is designed to layout a set of plots. For general | |
grid layout, use the :func:`~bokeh.layouts.layout` function. | |
Args: | |
children (list of lists of |Plot|): An array of plots to display in a | |
grid, given as a list of lists of Plot objects. To leave a position | |
in the grid empty, pass None for that position in the children list. | |
OR list of |Plot| if called with ncols. OR an instance of GridSpec. | |
sizing_mode (``"fixed"``, ``"stretch_both"``, ``"scale_width"``, ``"scale_height"``, ``"scale_both"`` ): How | |
will the items in the layout resize to fill the available space. | |
Default is ``"fixed"``. For more information on the different | |
modes see :attr:`~bokeh.models.LayoutDOM.sizing_mode` | |
description on :class:`~bokeh.models.LayoutDOM`. | |
toolbar_location (``above``, ``below``, ``left``, ``right`` ): Where the | |
toolbar will be located, with respect to the grid. Default is | |
``above``. If set to None, no toolbar will be attached to the grid. | |
ncols (int, optional): Specify the number of columns you would like in your grid. | |
You must only pass an un-nested list of plots (as opposed to a list of lists of plots) | |
when using ncols. | |
width (int, optional): The width you would like all your plots to be | |
height (int, optional): The height you would like all your plots to be. | |
toolbar_options (dict, optional) : A dictionary of options that will be | |
used to construct the grid's toolbar (an instance of | |
:class:`~bokeh.models.ToolbarBox`). If none is supplied, | |
ToolbarBox's defaults will be used. | |
merge_tools (``True``, ``False``): Combine tools from all child plots into | |
a single toolbar. | |
Returns: | |
Row or Column: A row or column containing the grid toolbar and the grid | |
of plots (depending on whether the toolbar is left/right or | |
above/below. The grid is always a Column of Rows of plots. | |
Examples: | |
>>> gridplot([[plot_1, plot_2], [plot_3, plot_4]]) | |
>>> gridplot([plot_1, plot_2, plot_3, plot_4], ncols=2, width=200, height=100) | |
>>> gridplot( | |
children=[[plot_1, plot_2], [None, plot_3]], | |
toolbar_location='right' | |
sizing_mode='fixed', | |
toolbar_options=dict(logo='gray') | |
) | |
''' | |
if plot_width is not None or plot_height is not None: | |
deprecated((2, 4, 0), "plot_width and plot_height", "width or height") | |
if toolbar_options is None: | |
toolbar_options = {} | |
if toolbar_location: | |
if not hasattr(Location, toolbar_location): | |
raise ValueError(f"Invalid value of toolbar_location: {toolbar_location}") | |
children = _parse_children_arg(children=children) | |
if ncols: | |
if any(isinstance(child, list) for child in children): | |
raise ValueError("Cannot provide a nested list when using ncols") | |
children = list(_chunks(children, ncols)) | |
# Additional children set-up for grid plot | |
if not children: | |
children = [] | |
# Make the grid | |
toolbars: List[Toolbar] = [] | |
items: List[Tuple[LayoutDOM, int, int]] = [] | |
for y, row in enumerate(children): | |
for x, item in enumerate(row): | |
if item is None: | |
continue | |
elif isinstance(item, LayoutDOM): | |
if merge_tools: | |
for plot in item.select(dict(type=Plot)): | |
toolbars.append(plot.toolbar) | |
plot.toolbar_location = None | |
if isinstance(item, Plot): | |
if plot_width is not None: | |
item.width = plot_width | |
if plot_height is not None: | |
item.height = plot_height | |
if width is not None: | |
item.width = width | |
if height is not None: | |
item.height = height | |
if sizing_mode is not None and _has_auto_sizing(item): | |
item.sizing_mode = sizing_mode | |
items.append((item, y, x)) | |
else: | |
raise ValueError("Only LayoutDOM items can be inserted into a grid") | |
if not merge_tools or not toolbar_location: | |
return GridBox(children=items, sizing_mode=sizing_mode) | |
grid = GridBox(children=items) | |
tools = sum([ toolbar.tools for toolbar in toolbars ], []) | |
proxy = ProxyToolbar(toolbars=toolbars, tools=tools, **toolbar_options) | |
toolbar = ToolbarBox(toolbar=proxy, toolbar_location=toolbar_location) | |
if toolbar_location == 'above': | |
return Column(children=[toolbar, grid], sizing_mode=sizing_mode) | |
elif toolbar_location == 'below': | |
return Column(children=[grid, toolbar], sizing_mode=sizing_mode) | |
elif toolbar_location == 'left': | |
return Row(children=[toolbar, grid], sizing_mode=sizing_mode) | |
elif toolbar_location == 'right': | |
return Row(children=[grid, toolbar], sizing_mode=sizing_mode) | |
# XXX https://github.com/python/mypy/issues/731 | |
def grid(children: List[LayoutDOM | List[LayoutDOM | List[Any]]], *, sizing_mode: SizingModeType | None = ...) -> GridBox: ... | |
def grid(children: Row | Column, *, sizing_mode: SizingModeType | None = ...) -> GridBox: ... | |
def grid(children: List[LayoutDOM | None], *, sizing_mode: SizingModeType | None = ..., nrows: int) -> GridBox: ... | |
def grid(children: List[LayoutDOM | None], *, sizing_mode: SizingModeType | None = ..., ncols: int) -> GridBox: ... | |
def grid(children: List[LayoutDOM | None], *, sizing_mode: SizingModeType | None = ..., nrows: int, ncols: int) -> GridBox: ... | |
def grid(children: str, *, sizing_mode: SizingModeType | None = ...) -> GridBox: ... | |
def grid(children: Any = [], sizing_mode: SizingModeType | None = None, nrows: int | None = None, ncols: int | None = None) -> GridBox: | |
""" | |
Conveniently create a grid of layoutable objects. | |
Grids are created by using ``GridBox`` model. This gives the most control over | |
the layout of a grid, but is also tedious and may result in unreadable code in | |
practical applications. ``grid()`` function remedies this by reducing the level | |
of control, but in turn providing a more convenient API. | |
Supported patterns: | |
1. Nested lists of layoutable objects. Assumes the top-level list represents | |
a column and alternates between rows and columns in subsequent nesting | |
levels. One can use ``None`` for padding purpose. | |
>>> grid([p1, [[p2, p3], p4]]) | |
GridBox(children=[ | |
(p1, 0, 0, 1, 2), | |
(p2, 1, 0, 1, 1), | |
(p3, 2, 0, 1, 1), | |
(p4, 1, 1, 2, 1), | |
]) | |
2. Nested ``Row`` and ``Column`` instances. Similar to the first pattern, just | |
instead of using nested lists, it uses nested ``Row`` and ``Column`` models. | |
This can be much more readable that the former. Note, however, that only | |
models that don't have ``sizing_mode`` set are used. | |
>>> grid(column(p1, row(column(p2, p3), p4))) | |
GridBox(children=[ | |
(p1, 0, 0, 1, 2), | |
(p2, 1, 0, 1, 1), | |
(p3, 2, 0, 1, 1), | |
(p4, 1, 1, 2, 1), | |
]) | |
3. Flat list of layoutable objects. This requires ``nrows`` and/or ``ncols`` to | |
be set. The input list will be rearranged into a 2D array accordingly. One | |
can use ``None`` for padding purpose. | |
>>> grid([p1, p2, p3, p4], ncols=2) | |
GridBox(children=[ | |
(p1, 0, 0, 1, 1), | |
(p2, 0, 1, 1, 1), | |
(p3, 1, 0, 1, 1), | |
(p4, 1, 1, 1, 1), | |
]) | |
""" | |
class row: | |
children: List[row | col] | |
class col: | |
children: List[row | col] | |
class Item: | |
layout: LayoutDOM | |
r0: int | |
c0: int | |
r1: int | |
c1: int | |
class Grid: | |
nrows: int | |
ncols: int | |
items: List[Item] | |
def flatten(layout) -> GridBox: | |
def gcd(a: int, b: int) -> int: | |
a, b = abs(a), abs(b) | |
while b != 0: | |
a, b = b, a % b | |
return a | |
def lcm(a: int, *rest: int) -> int: | |
for b in rest: | |
a = (a*b) // gcd(a, b) | |
return a | |
def nonempty(child: Grid) -> bool: | |
return child.nrows != 0 and child.ncols != 0 | |
def _flatten(layout: row | col | LayoutDOM) -> Grid: | |
if isinstance(layout, row): | |
children = list(filter(nonempty, map(_flatten, layout.children))) | |
if not children: | |
return Grid(0, 0, []) | |
nrows = lcm(*[ child.nrows for child in children ]) | |
ncols = sum(child.ncols for child in children) | |
items: List[Item] = [] | |
offset = 0 | |
for child in children: | |
factor = nrows//child.nrows | |
for i in child.items: | |
items.append(Item(i.layout, factor*i.r0, i.c0 + offset, factor*i.r1, i.c1 + offset)) | |
offset += child.ncols | |
return Grid(nrows, ncols, items) | |
elif isinstance(layout, col): | |
children = list(filter(nonempty, map(_flatten, layout.children))) | |
if not children: | |
return Grid(0, 0, []) | |
nrows = sum(child.nrows for child in children) | |
ncols = lcm(*[ child.ncols for child in children ]) | |
items = [] | |
offset = 0 | |
for child in children: | |
factor = ncols//child.ncols | |
for i in child.items: | |
items.append(Item(i.layout, i.r0 + offset, factor*i.c0, i.r1 + offset, factor*i.c1)) | |
offset += child.nrows | |
return Grid(nrows, ncols, items) | |
else: | |
return Grid(1, 1, [Item(layout, 0, 0, 1, 1)]) | |
grid = _flatten(layout) | |
children = [] | |
for i in grid.items: | |
if i.layout is not None: | |
children.append((i.layout, i.r0, i.c0, i.r1 - i.r0, i.c1 - i.c0)) | |
return GridBox(children=children) | |
layout: row | col | |
if isinstance(children, list): | |
if nrows is not None or ncols is not None: | |
N = len(children) | |
if ncols is None: | |
ncols = math.ceil(N/nrows) | |
layout = col([ row(children[i:i+ncols]) for i in range(0, N, ncols) ]) | |
else: | |
def traverse(children: List[LayoutDOM], level: int = 0): | |
if isinstance(children, list): | |
container = col if level % 2 == 0 else row | |
return container([ traverse(child, level+1) for child in children ]) | |
else: | |
return children | |
layout = traverse(children) | |
elif isinstance(children, LayoutDOM): | |
def is_usable(child: LayoutDOM) -> bool: | |
return _has_auto_sizing(child) and child.spacing == 0 | |
def traverse(item: LayoutDOM, top_level: bool = False): | |
if isinstance(item, Box) and (top_level or is_usable(item)): | |
container = col if isinstance(item, Column) else row | |
return container(list(map(traverse, item.children))) | |
else: | |
return item | |
layout = traverse(children, top_level=True) | |
elif isinstance(children, str): | |
raise NotImplementedError | |
else: | |
raise ValueError("expected a list, string or model") | |
grid = flatten(layout) | |
if sizing_mode is not None: | |
grid.sizing_mode = sizing_mode | |
for child in grid.children: | |
layout = child[0] | |
if _has_auto_sizing(layout): | |
layout.sizing_mode = sizing_mode | |
return grid | |
#----------------------------------------------------------------------------- | |
# Dev API | |
#----------------------------------------------------------------------------- | |
class GridSpec: | |
""" Simplifies grid layout specification. """ | |
nrows: int | |
ncols: int | |
_arrangement: Dict[Tuple[int, int], LayoutDOM | None] | |
def __init__(self, nrows: int, ncols: int) -> None: | |
self.nrows = nrows | |
self.ncols = ncols | |
self._arrangement = {} | |
from .util.deprecation import deprecated | |
deprecated("'GridSpec' is deprecated and will be removed in Bokeh 3.0") | |
def __setitem__(self, key: Tuple[int | slice, int | slice], obj: LayoutDOM | List[LayoutDOM] | List[List[LayoutDOM]]) -> None: | |
k1, k2 = key | |
row1: int | |
row2: int | None | |
if isinstance(k1, slice): | |
row1, row2, _ = k1.indices(self.nrows) | |
else: | |
if k1 < 0: | |
k1 += self.nrows | |
if k1 >= self.nrows or k1 < 0: | |
raise IndexError("index out of range") | |
row1, row2 = k1, None | |
col1: int | |
col2: int | None | |
if isinstance(k2, slice): | |
col1, col2, _ = k2.indices(self.ncols) | |
else: | |
if k2 < 0: | |
k2 += self.ncols | |
if k2 >= self.ncols or k2 < 0: | |
raise IndexError("index out of range") | |
col1, col2 = k2, None | |
# gs[row, col] = obj | |
# gs[row1:row2, col] = [...] | |
# gs[row, col1:col2] = [...] | |
# gs[row1:row2, col1:col2] = [[...], ...] | |
T = TypeVar("T") | |
def get(obj: List[T] | None, i: int) -> T | None: | |
return obj[i] if obj is not None and 0 <= i < len(obj) else None | |
if row2 is None and col2 is None: | |
assert isinstance(obj, LayoutDOM) | |
self._arrangement[row1, col1] = obj | |
elif row2 is None: | |
assert col2 is not None | |
_obj = cast(List[LayoutDOM], obj) | |
for col in range(col1, col2): | |
self._arrangement[row1, col] = get(_obj, col - col1) | |
elif col2 is None: | |
assert row2 is not None | |
_obj = cast(List[LayoutDOM], obj) | |
for row in range(row1, row2): | |
self._arrangement[row, col1] = get(_obj, row - row1) | |
else: | |
_obj = cast(List[List[LayoutDOM]], obj) | |
for row, col in zip(range(row1, row2), range(col1, col2)): | |
self._arrangement[row, col] = get(get(_obj, row - row1), col - col1) | |
def __iter__(self) -> Iterator[List[LayoutDOM | None]]: | |
array: List[List[LayoutDOM | None]] = [ [None]*self.ncols for _ in range(0, self.nrows) ] | |
for (row, col), obj in self._arrangement.items(): | |
array[row][col] = obj | |
return iter(array) | |
#----------------------------------------------------------------------------- | |
# Private API | |
#----------------------------------------------------------------------------- | |
def _has_auto_sizing(item: LayoutDOM) -> bool: | |
return item.sizing_mode is None and item.width_policy == "auto" and item.height_policy == "auto" | |
L = TypeVar("L", bound=LayoutDOM) | |
def _parse_children_arg(*args: L | List[L] | GridSpec, children: List[L] | None = None) -> List[L] | GridSpec: | |
# Set-up Children from args or kwargs | |
if len(args) > 0 and children is not None: | |
raise ValueError("'children' keyword cannot be used with positional arguments") | |
if not children: | |
if len(args) == 1: | |
[arg] = args | |
if isinstance(arg, (GridSpec, list)): | |
return arg | |
return list(args) | |
return children | |
def _handle_child_sizing(children: List[LayoutDOM], sizing_mode: SizingModeType | None, *, widget: str) -> None: | |
for item in children: | |
if not isinstance(item, LayoutDOM): | |
raise ValueError(f"Only LayoutDOM items can be inserted into a {widget}. Tried to insert: {item} of type {type(item)}") | |
if sizing_mode is not None and _has_auto_sizing(item): | |
item.sizing_mode = sizing_mode | |
def _create_grid(iterable: Iterable[LayoutDOM | List[LayoutDOM]], sizing_mode: SizingModeType | None, layer: int = 0, **kwargs) -> Row | Column: | |
"""Recursively create grid from input lists.""" | |
return_list: List[LayoutDOM] = [] | |
for item in iterable: | |
if isinstance(item, list): | |
return_list.append(_create_grid(item, sizing_mode, layer + 1)) | |
elif isinstance(item, LayoutDOM): | |
if sizing_mode is not None and _has_auto_sizing(item): | |
item.sizing_mode = sizing_mode | |
return_list.append(item) | |
else: | |
raise ValueError( | |
"""Only LayoutDOM items can be inserted into a layout. | |
Tried to insert: %s of type %s""" % (item, type(item)) | |
) | |
if layer % 2 == 0: | |
return column(children=return_list, sizing_mode=sizing_mode, **kwargs) | |
else: | |
return row(children=return_list, sizing_mode=sizing_mode, **kwargs) | |
T = TypeVar("T") | |
def _chunks(l: Sequence[T], ncols: int) -> Iterator[Sequence[T]]: | |
"""Yield successive n-sized chunks from list, l.""" | |
assert isinstance(ncols, int), "ncols must be an integer" | |
for i in range(0, len(l), ncols): | |
yield l[i: i + ncols] | |
#----------------------------------------------------------------------------- | |
# Code | |
#----------------------------------------------------------------------------- | |