Grid
The NodeBox Grid library offers a number of tools to work with rows and columns in a page layout. It's like having a combination of InDesign and Excel available in NodeBox. You can use the library to generate flowing columns of text and image grids, automatically apply aesthetic proportions between cells in the grid, create spreadsheet tables with (simple) statistics, define reusable visual styles in a CSS-like way. Numerous of text commands help you for example determine good, legible column widths, apply widow/orphan control to paragraphs, generate lorem ipsum placeholder text.
Central concepts in this library are:
- Grid: rows and columns containing cells. Each cell can be a new grid.
- Content: a text or a list of images that is divided among the rows and columns in a grid.
- Proportion: aesthetic relations between the cells in a grid.
- Style: a description of colors, typography, whitespace that can be attached to cells.
The library has a caching mechanism that allows you to create multi-page documents (e.g. a book), keeping track of the text and images that have already been drawn along the way.
Download
![]() | grid.zip (60KB) Last updated for NodeBox 1.9.4.2 Licensed under GPL Author: Tom De Smedt |
Documentation
- How to get the library up and running
- Creating a grid
- Splitting and navigating the grid
- Adding content
- Working with text
- Working with styles
- Working with proportions
- Resizing cells
- Spreadsheet statistics
- Multiple pages
How to get the library up and running
Put the grid library folder in the same folder as your script so NodeBox can find the library. You can also put it in ~/Library/Application Support/NodeBox/.
grid = ximport("grid")
Creating a grid
create(rows=1, columns=1, width=None, height=None)
The create() command returns a new grid object, by default encompassing the entire drawing canvas. You can define the number of rows and columns in the grid and a different width and/or height. A grid also has lots of properties and methods to make life easier. We'll briefly summarize them here and discuss them in detail later on.
The returned grid object has the following properties:
Navigation
- grid.name: the name of the grid.
- grid.parent: the grid in which this grid is a cell, None for the top-level grid.
- grid.root: the top-level grid, e.g. the container for all other grid cells.
- grid.rows: a list of rows in the grid. Each row is a list of contained grid cells.
- grid.columns: a list of columns in the grid. Each column is a list of contained grid cells.
- grid.top: the row or cell at the top in the grid.
- grid.bottom: the row or cell at the bottom in the grid.
- grid.left: the column or cell at the utmost left in the grid.
- grid.right: the column or cell at the utmost right in grid.
Position and size
- grid.x: the horizontal offset of a cell.
- grid.y: the vertical offset of a cell.
- grid.width: a cell's width in pixels.
- grid.height: a cell's height in pixels.
- grid.relative_width: a cell's width as a number between 0.0 and 1.0, relative to other cells.
- grid.relative_height: a cell's height as a number between 0.0 and 1.0, in relative to other cells.
- grid.fixed: True when this cell does not harmoniously rescale to make room for other cells.
Content
- grid.content: the content for the grid, divided among the rows and columns.
- grid.content_width: the actual width available for content, minus whitespace.
- grid.content_height: the actual height available for content, minus whitespace.
- grid.flow: a list of cells in the grid, defining the flow of content.
Style and proportion
- grid.style: the name of the current visual style.
- grid.styles: a dictionary of available styles.
- grid.proportion: a collection of properties defining how cells are spaced in relation to each other.
Each row and column in the grid has the same properties available, with the exception of rows, columns, proportion and flow. This enables you to set the size, style or content for all the cells in a row or column in one go.
The grid.draw() method draws the grid to the canvas, calculating text flow and applying styles.
The grid.copy() method returns a new grid object that is an exact copy of this one.
grid.draw(x=0, y=0)
grid.copy(parent=None)
You can loop through the grid like a list of cells:
for cell in grid: print cell.name
Splitting and navigating the grid
The grid object has a grid.split() method that divides the grid into rows and columns of cells. It can be supplied an optional proportion and style name. The grid.clear() method removes all cells (but it retains the grid's style, proportion and content).
grid.split(rows, columns, proportion=None, style=None)
grid.clear()
To fully grasp the way a grid works you need to understand its recursive nature. A grid is made up of cells, arranged into rows and columns. Each cell is a new grid object. Take a look at the example below. We split a grid into two rows and two columns. Since the top-level cell is a grid object as well, we can use its split() method to subdivide the layout further.
|
| g = grid.create(2, 2, 150, 150) g.cell(0,0).split(2, 2) g.styles.margin = 1 g.styles.background = ( color(0.5, 0.55, 0.6), color(0.4, 0.45, 0.5) ) g.draw() |
Navigation
A grid has the following methods to retrieve a row, column or single cell:
grid.row(i)
grid.column(j)
grid.cell(i,j)
grid(i,j)
Just as with lists the supplied indices start from zero (so the first row has index 0 and not 1). You can also use the rows and columns properties, which are both lists.
The top, left, bottom and right properties are also very useful to retrieve rows and columns at the edge of the grid. You may already have guessed that the recursive structure of a grid can go very deep, leading to unwieldy statements such as:
grid.left[0].column(1).cell(2, 2)
Luckily, like the grid object, each row and column also has the top, left, bottom, right properties.
Here are some examples:
![]() | g.column(0) g.left | ![]() | g.top.right |
![]() | g.row(0) g.top | ![]() | g.top.left.bottom |
![]() | g.rows.second |
As demonstrated in the final example, some other handy aliases are the first, second, third, ... sixth properties. They will work on a grid, a row, a column and even on a list of rows or columns.
Another good idea is to label distinct sections in the grid. For example, you may want to call the top part "header" and work with that label from there on. Each named section in the grid (cell, row, column) automatically becomes available as a property. You can also retrieve named sections with the grid.find() method.
g.top.name = "header" g.header.style = "header_style" s = g.styles.create("header_style") s.fontsize = 20
cell = g.find("header")
Highlight helper
The Grid library has a highlight() command that can help you out navigating to the structure of a grid. You call it after drawing the grid to the canvas. It will overlay the grid with indicators marking all the cells. When the optional recursive parameter is True, it will keep highlighting cells in cells, and so on.
highlight(grid, clr=None, recursive=False)
As you can see in the above example, you supply a grid object (or a column or row) to the command and it will indicate the suffix you need to add in your code to reference a particular cell in it.
Adding content
You can fill a grid with text, images or your own drawing command. Simply assign it to the grid.content property. The content automatically gets distributed among the grid's rows and columns, following a "depletion"-approach: cells are filled until there is no content left. The grid.flow list determines which cells get content first (column-first by default).
g = grid.create(1, 3, width=550, height=200) g.content = open("alice.txt").read() # text from a file g.draw()
The following types of content can be added to the grid:
- text: a string of text
- image: a single image filename or a list of image filenames
- command: a custom drawing command
- number
After you set the content, the following properties become available:
- grid.content.widows: how many stray lines at the end to move to the next cell (1 by default)
- grid.content.orphans: how many stray lines at the start to move the previous cell (1 by default)
- grid.content.repeat: when True, cycle through the list of images until all cells are filled.
- grid.content.remainder: the excess content that wasn't drawn.
Some methods you can use to retrieve content:
grid.content.is_text()
grid.content.is_number()
grid.content.is_image()
grid.content.is_command()
str(grid.content) # retrieve the text content
int(grid.content) # retrieve numeric content
float(grid.content) # retrieve numeric content
list(grid.content) # retrieve the list of images
Again, keep in mind that since each cell in a grid row or column is itself a grid object, you can add content (titles, subtitles, images, ...) to any individual cell.
grid.has_content()
The grid.has_content() returns True if a cell has content, False if it is simply inheriting content from a parent grid.
Flowing content
When a grid is drawn, the content is distributed among its rows and columns. For text content, this means that a piece is cut off from the start of the text so that it matches the size of the current cell (taking into account whitespace, font size and alignment). The remainder of the text is then passed on to the next cell in the flow. Content can flow vertically from top to bottom (column-first), horizontally from left to right (row-first) or in your own custom way.
The grid object has two methods for setting the flow:
grid.flow_vertical(recursive=True)
grid.flow_horizontal(recursive=True)
Additionally, any cells in a grid you add to the grid.flow list will get content first.
Fitting content
In some situations you don't want content to flow from cell to cell, you want it to match the size of this cell as best as possible. This is especially true for grids containing images and titles. We may want the image in a cell to stretch so it fits the cell comfortably, avoiding gaps. We may want the title to take up all the available space so we can then resize and experiment with the grid without having to worry about fontsize anymore.
To accomplish this we give the cell a style which has its style.fit property set to True:
| g = grid.create(1, 1, 250, 90) g.styles.default.fit = True g.styles.default.align = "center" g.content = "heading" g.draw() |
| g = grid.create(2, 2, 250, 250) g.top.left.split(2, 2) g.top.left.bottom.right.split(2, 2) # fetch images from Morguefile.com web = ximport("web") img = web.morguefile.search("ocean")[:5] img = [x.download() for x in img] g.content = images g.content.repeat = True g.styles.margin = 1 g.styles.fit = True g.styles.align = "center", "bottom" |
Custom content
Instead of adding text or images to the grid, you can also add a custom command as content. This is great if you want to showcase different versions of generative artwork, add embellishments to the layout or document code, for example. The custom command takes the following form:
custom_content(x, y, width, height, style=None)
It receives the grid's position, size and style as input parameters.
| def ovals(x, y, w, h, style=None): for i in range(20): r = random(5, 40) stroke(0.1, 0.4) strokewidth(0.5) fill(random(), 0.1, 0.2, 0.4) oval( x + random(w) - r, y + random(h) - r, r*2, r*2 ) g = grid.create(2, 2, 250, 250) g.styles. |











