Layout
A grid is defined by a layout, written as ASCII or JSON, and a mapping, which binds its panels to content.
AI-assisted, human-approvednovem uses AI to review and keep our documentation up to date.
A grid has two content files:
layout— the geometry: which panels exist and how big they are. Write an ASCII drawing withContent-Type: text/plainor a JSON definition withContent-Type: application/json.mapping— the content: which reference goes in each panel. Write this file withContent-Type: text/plain.
Writing either file re-parses both, so they can be updated independently and
in any order. The parsed result is available as JSON under the grid's
json/data.json (structure) and json/meta_data.json (mapping refs).
ASCII layouts
The layout is a rectangle drawn with three characters: +, - and |.
Boxes are split into smaller boxes by divider lines, and each innermost box
holds one instruction naming the plot label to show there:
+-------------+----------+
| | |
| {A:-} | {B:-} |
| | |
| | |
| | |
+-------------+----------+
The rules:
- One outer frame. The whole drawing is one rectangle; blank rows and columns around it are ignored.
- Dividers split a box in two (or more). A divider is a full row of
-/+(horizontal split) or a full column of|/+(vertical split) running edge to edge inside a box. A divider can't mix-and|. - A pipe-ended horizontal divider floats.
|---------|creates a floating vertical stack. Its ASCII heights remain the default allocation, and the parsed wrapper is marked with"floating": true; fixed wrappers omit the field. Current renderers preserve the default allocation and do not redistribute unused height. Boundary-connected+---------+dividers stay fixed and are resolved first, so floating stacks nest inside the fixed layout. - Splits nest. Each sub-box can be divided again, alternating directions as needed. Any rectangular arrangement is expressible.
- Size is proportional. The distance between a box's border characters becomes its share of the rendered dashboard. Shared divider characters have no size of their own. In the example above the left panel spans 14 of 25 horizontal units, so it gets 56% of the width.
- One instruction per box. Whatever remains in an innermost box after
removing drawing characters and whitespace must be a single
{LABEL}instruction.
Instructions
{LABEL[:POSITION]}
LABEL— an alphanumeric label (A,B2,perf, …) defined in the mapping file. A label with no matching mapping line leaves the cell empty.POSITION— an optional vertical anchor for the cell's content:
| Position | Meaning |
|---|---|
^ | top |
- | middle (default) |
v | bottom |
{A}, {A:} and {A:-} are equivalent. Any other position character makes
the instruction invalid and the cell renders empty. Position anchors content
within the cell's current allocation.
JSON layouts
POST a versioned layout tree to the same layout endpoint:
{
"version": 1,
"layout": {
"type": "split",
"id": "dashboard",
"direction": "horizontal",
"sizes": [37.125, 62.875],
"children": [
{ "type": "panel", "id": "revenue", "label": "A" },
{
"type": "split",
"direction": "vertical",
"children": [
{ "type": "panel", "id": "costs", "label": "B", "position": "top" },
{ "type": "panel", "id": "margin", "label": "C" }
]
}
]
}
}
The left panel takes 37.125% of the width. The right side contains two panels
with equal heights. The labels A, B and C use the same mapping file
as an ASCII layout.
| Node | Required fields | Optional fields |
|---|---|---|
| Definition | version: 1, layout | None |
| Split | type: "split", direction, children | id, sizes, floating |
| Panel | type: "panel" | id, label, position |
directionishorizontalfor children beside each other orverticalfor children above each other. A split needs at least two children.sizescontains one positive, finite number per child. These are relative weights, so[3, 1]and[75, 25]both allocate 75% to the first child. Omit it for equal sizes. Splits can nest in either direction.ididentifies a node independently of its content and position. IDs must be unique within the layout, contain only ASCII letters, digits,_and-, start with a letter or digit, and be at most 128 characters long. Two panels may use the same mapping label while having different IDs.labelis an alphanumeric mapping label. Omit it for an empty panel. A label with no mapping also leaves the panel empty.positionistop,middle(default) orbottom.floating: trueis accepted only on vertical splits. It produces the same floating allocation flag as an ASCII pipe-ended divider.
Version 1 rejects unknown fields and node types. Layouts are limited to 32 nested edges and 1,024 nodes, including the root. Size allocations must stay positive after normalization.
GET on layout preserves the saved source by default, with a text/plain
response type. The same applies when Accept includes an accepted wildcard,
such as the common application/json, text/plain, */* client default.
To request a representation, send Accept: application/json for JSON geometry
or Accept: text/plain for an ASCII drawing. Without a wildcard, the preferred
media type is chosen by quality weight (q) and then header order; q=0 types
are excluded. JSON source is returned byte-for-byte when JSON is requested.
ETag hashes the returned bytes, X-NVM-Source-ETag identifies the saved source
for conditional writes, and Vary: Accept distinguishes cached representations.
Generated representations must match the current source revision. A missing or stale modern representation returns 406 while processing catches up. Legacy ASCII grids without a source-revision marker return their saved text instead, until the worker generates their representations.
Either syntax can replace the other. JSON and ASCII compile to the same render
structure, with JSON node IDs also included. A valid JSON object selects the
JSON parser; bare cells such as {A} and dash-bordered drawings remain ASCII.
Mapping updates reparse the saved layout in its original syntax.
As with ASCII, layout validation runs in the grid worker after source storage.
JSON syntax errors in an application/json request are rejected by the API
before storage. Invalid layout fields or sizes return 400 with a message
naming the problem, such as Invalid fields at layout definition: unexpected tabs. The saved source is not rolled back, and the parsed JSON retains its
last successful result until a valid layout is processed.
The mapping file
The mapping file binds layout labels to plots, one binding per line:
LABEL => REFERENCE[,PARAM=VALUE]...
For example:
A => /u/novem_demo/p/nei_rgn_perf,c=f
B => https://novem.io/u/novem_demo/p/nei_rgn_tb,c=t,h=f,m=d,s=2,tbg=f
LABEL— the label used in the layout file.REFERENCE— a plot shortname or path. The equivalent forms/p/my_plot,p/my_plot,/u/you/p/my_plot,u/you/p/my_plot, andhttps://novem.io/u/you/p/my_plotare all accepted.- Comma options are the preferred form because they are concise and easy to
type, for example
/u/you/p/my_plot,m=d,s=2. - Standard query parameters are also accepted, so a working Novem URL such as
/u/you/p/my_plot?m=d&scale=2can be pasted directly into a mapping. If both forms set the same option, the later comma option wins. Whitespace anywhere on the line is ignored, and lines without a=>are skipped.
Parameters
| Parameter | Alias | Values | Default | Description |
|---|---|---|---|---|
include_header | h | t, true, f, false | false | Include the plot's header in the cell |
include_caption | c | t, true, f, false | false | Include the plot's caption in the cell |
mode | m | l, light, d, dark | inherit | Render this cell in light or dark mode |
scale | s | 1, 2, 3 | consumer default | Set the device or raster scale factor |
transparent_background | tbg | t, true, f, false | consumer default | Request a transparent or filled background |
An unrecognised value falls back to the parameter's default. The preferred
comma form uses the compact s=2 alias and stores it as scale: 2. Query
strings should use /i's canonical scale=2 spelling because /i reserves
s for legacy image-size codes.
These are per-cell editorial controls, like the options on a
{{ vis }} block in a document
or e-mail. If m, scale or tbg is omitted, the field is left out of
metadata so a TV, renderer or other consumer can inherit its surrounding
context.
Note: if the same label appears on several mapping lines, the last line wins. Labels that exist in the mapping but not in the layout are simply unused.
Reading the parsed result
After a successful write the parsed layout is exposed on the grid's JSON endpoints, useful for debugging a layout that doesn't split the way you expected:
curl -H "Authorization: Bearer $NOVEM_TOKEN" \
https://api.novem.io/v1/vis/grids/my_dashboard/json/data.json
{
"structure": {
"type": "wrapper",
"width": 25,
"height": 6,
"children": [
{
"type": "wrapper container",
"width": 14,
"height": 6,
"children": [],
"inst": { "lid": "NL--hwckacar", "position": "middle", "include_header": false, "include_caption": false }
},
{
"type": "wrapper container",
"width": 11,
"height": 6,
"children": [],
"inst": { "lid": "NL--tguxadkh", "position": "middle", "include_header": false, "include_caption": true }
}
]
}
}
The lid values are internal link ids connecting each cell to its mapping
entry. json/meta_data.json lists them alongside the labels and normalized
per-cell presentation options:
curl -H "Authorization: Bearer $NOVEM_TOKEN" \
https://api.novem.io/v1/vis/grids/my_dashboard/json/meta_data.json
{
"refs": [
{
"label": "B",
"lid": "NL--tguxadkh",
"include_header": false,
"include_caption": true,
"mode": "dark",
"scale": 2,
"transparent_background": false
}
]
}
See also
- Grid API — every grid endpoint.
- API overview — verbs, permissions and the filesystem metaphor.