On this page

Historical material

This page preserves an earlier release or study. See the current guide for supported behavior.

Inklet 2.5

Version 2.5 adds nested grids, measured compositions and architecture modules, deferred annotation placement, publication presets, and saved-revision review. The existing Diagram, Panel, Figure and v2 document APIs remain available.

Build the complete example:

inklet build examples/v25_document.py --output out/v25 --name document
inklet watch examples/v25_document.py --output out/v25 --name document

Nested panels

A subfigure uses the document grid API. Add plots, components or more subfigures; each nested grid inherits the enclosing document's theme and shares its geometry cache. The complete page is painted and checked once. Minimum sizes remain physical constraints, and unsatisfiable layouts raise LayoutError.

import inklet as i

left = i.plot_spec(x=(0, 2), y=(0, 5)).line([(0, 1), (1, 3), (2, 2)]).axes(key='axes')
right = i.plot_spec(x=(0, 2), y=(0, 5)).line([(0, 2), (1, 4), (2, 3)]).axes()
panels = i.subfigure(columns=2, gap=6).letters(start='a')
panels.add('control', left, row=0, column=0, min_height=45)
panels.add('treatment', right, row=0, column=1, min_height=45)

doc = i.publication('double-column').document()
doc.add('experiment', panels)
doc['experiment']['control'].replace('axes', x='Time')
compiled = doc.compile()

letters() measures the labels as part of the grid's furniture. Plots in the same row share top and bottom margins, so different axes, legends or panel letters do not change their data-region alignment. Lettered responsive components also reserve space for the letter. start, style, corner, pad, inside and size use the existing inklet.letters() options.

Measured compositions and modules

Use composition() for an irregular arrangement. Children have stable names; their positions and dimensions can depend on the page size, other children's measured extents, or registered anchors. Forward references are allowed. Cyclic measurements or placements fail with a named layout error.

art = i.composition(150, 50)
art.add('input', i.module('Observations'), x=5, y=15)
x, y = art.point('input', 'out')
art.add('model', i.module('Shared encoder'), x=x + 12, y=15)
x, y = art.point('model', 'out')
art.add('output', i.module('Prediction'), x=x + 12, y=15)
art.link('input:out', 'model:in')
art.link('model:out', 'output:in')
art.constrain(art.page_width - art.point('output', 'e')[0], minimum=5,
              message='Increase the page width for these module labels')

doc = i.document(width=160, height=60)
doc.add('architecture', art)
first = doc.compile()
art['model'].configure('A longer encoder label')
second = doc.compile()  # downstream components and connections move

Modules size their boxes from measured text and padding. Define named ports with fractions of the box, for example ports={'input': (0, .5), 'upper': (1, .25), 'lower': (1, .75)}. min_width, min_height, max_height, pad, label_offset, text_style and box_style control physical appearance. Font sizes and strokes stay unchanged when the page grows. Module dimensions are intrinsic; a narrow cell reports insufficient space instead of squeezing text.

Composition coordinates default to millimetres. unit=250/2157 is useful when reconstructing artwork in source-image coordinates: coordinates and expressions use that unit, while component styles remain physical millimetres. Explicit child width and height use composition units. For plots they describe the data region, not the full extent of the axes, legend or inset.

Placement defaults to the child's existing local coordinate frame. Set anchor='nw', 'center', a registered port, or 'area-nw' for measured alignment. Use measure(name, 'width'), measure(name, 'height'), page_width and page_height in arithmetic expressions. Expressions can also occur inside component() arguments and connection waypoints. replace(name, item) retains a child's placement constraints. bindings can hold named data and category definitions, accessible through the same composition[name] lookup.

link() routes named branches and returns after placement. annotate() places callouts after all components and connections exist, then runs the joint label placer. That placer chooses sides for the group; remaining conflicts are still reported by diagnostics. A composition's declared rectangle is its layout frame, not a clipping mask: artwork extending beyond the page remains diagnosable.

Plot annotations

PlotSpec now resolves marks, axes and keys, insets, significance brackets, callouts, then titles. You can declare a bracket before drawing its bars; its height still clears the current data and earlier brackets. Callouts consider the measured marks and furniture, including late insets and earlier callouts. Use avoid_marks=False on PlotSpec.annotate() for the previous placement policy. Explicit avoid= blockers and the native Panel annotation API remain available.

plot = i.plot_spec(x=i.band(['Control', 'Treatment']), y=(0, 10))
plot.bracket('Control', 'Treatment', '**')
plot.bars(['Control', 'Treatment'], [3, 7]).axes()

Placement cannot guarantee a collision-free result in an arbitrarily crowded panel. The review page retains all diagnostics so the author can inspect it.

Publication defaults

publication('single-column') uses an 89 mm page; publication('double-column') uses 183 mm. Both use 8 pt main type, 7 pt small type, 0.18 mm strokes, embedded fonts and 300 dpi previews. Print checks flag type below 6 pt, strokes below 0.1 mm and raster artwork below 300 dpi at its final transformed size. publication('slide') uses a 254 mm page with larger type and 150 dpi previews.

These are general authoring presets, not journal submission certifications. Override the profile to match the actual destination:

profile = i.publication('single-column', width=85, min_font_pt=7, dpi=450)
doc = profile.document(height=60)

profile.document() applies the theme, physical width, diagnostic thresholds and export settings together. Its compiled manifest records the profile. Explicit export(dpi=..., text=...) options take precedence. The CLI uses the profile's preview DPI unless --dpi is supplied. Profiles do not rescale artwork or upsample source images to make a print check pass.

Review revisions

Every review page supports component and severity filters, text search, and clickable diagnostic locations in the editable SVG. Parent-component filters include their nested children. JSON diagnostics preserve all findings and targets.

inklet build examples/v25_document.py --output out/first --name document
# Edit the authoring file, then compare with the completed bundle:
inklet build examples/v25_document.py --output out/second --name document \
  --compare-to out/first/document-manifest.json

Python: compiled.export('out/second', name='document', compare_to=previous_manifest). A directory is also accepted when it contains <name>-manifest.json.

Matching page dimensions, DPI and pixel dimensions enable an opacity slider and a difference image amplified five times. The reported fraction counts pixels whose change exceeds 25/255 in any RGB channel; it is a visual difference measure, not a scientific accuracy score. Different page sizes or resolutions are shown side by side without a pixel score or resampling. The previous PNG and manifest are copied into the new bundle, making the review portable.

watch automatically compares each successful build with the preceding bundle. Pass --compare-to to keep a fixed reference instead. Exports finish rendering and validating the comparison before replacing existing files; failed builds retain the last successful review.

Complete examples

The v2.5 document uses the composition APIs with nested grids, architecture modules and live plots. The twenty-panel stress test exercises larger mixed figures, edits, resizing, export diagnostics and provenance.

Search documentation

Type to search guides, examples and the API.