On this page
Live data and provenance¶
Use datasets and explicit references when plots must follow data edits.
Literal lists and NumPy arrays are snapshotted when an instruction is recorded.
Array snapshots retain their shape and dtype and are read-only; component
factories should create a working copy if they need to mutate an array. Explicit
replace() and configure() calls compare complete array contents, including
values omitted from NumPy’s text representation.
Run the Python blocks in order. The examples use core Inklet and simulated
data; PNG previews additionally need the render extra.
A table shared by plots¶
import inklet as i
data = i.dataset(
{'time': [0, 1, 2], 'control': [1, 2, 3], 'treatment': [2, 4, 5]},
name='experiment',
units={'time': 's', 'control': 'mV', 'treatment': 'mV'},
source=i.Source('Simulated example', method='simulated'),
)
y = i.shared_scale(data.column('control'), data.column('treatment'), include_zero=True)
doc = i.document(width=180, columns=2, share_plot_margins=True)
colours = {'control': '#176b9b', 'treatment': '#198c83'}
for column, name in enumerate(('control', 'treatment')):
p = i.plot_spec(x=(0, 2), y=y)
p.line(data.points('time', name), name=name.title(), stroke=colours[name])
p.axes(x='Time / s', y='Signal / mV').legend(side='bottom')
doc.add(name, p, row=0, column=column, min_height=50)
first = doc.compile()
data.update(treatment=[2, 4, 9])
second = doc.compile()
assert first.to_svg() != second.to_svg()
assert second.metadata['datasets'][0]['revision'] == 1

The first compiled snapshot.

The second snapshot. The control values are unchanged, but both plots use the new shared domain. The earlier snapshot retains its original scale and data.
All columns must have equal lengths. update() validates the complete table
before changing it; failed updates leave the old table intact. Changing the
number of rows requires updating every affected column in the same call.
Dataset cells snapshot nested arrays as tuples, so caller edits cannot change
the table without an explicit update(). Recorded component array arguments
retain their separate read-only ndarray snapshot behavior described above.
column() returns a live reference to one column; points() returns paired
coordinates. Use data.columns for the current immutable values.
Shared scales derive a domain from all supplied columns. Units must agree;
convert incompatible units before sharing. Use kind='log' for a logarithmic
shared scale and supply finite, positive values.
Explicit transformations¶
def subtract(time, control, treatment):
return tuple((x, b - a) for x, a, b in zip(time, control, treatment))
difference = i.derive(subtract, data.column('time'),
data.column('control'), data.column('treatment'))
delta = i.plot_spec(x=(0, 2), y=(0, 10))
delta.line(difference, name='Difference', stroke='#198c83').legend(side='bottom')
delta.axes(x='Time / s', y='Difference / mV')
delta_doc = i.document(width=100)
delta_doc.add('difference', delta, min_height=50)
assert 'Difference' in delta_doc.compile().to_svg()
derive() materializes its explicit inputs and calls the factory during
evaluation. Keep the function deterministic. Inputs hidden in a closure are
outside dependency tracking. Inklet draws supplied values; you remain
responsible for the statistical calculation and its interpretation.
Categories that survive filtering¶
encoding = i.CategoryEncoding(i.categories(
{'control': '#176b9b', 'treated': '#198c83'},
labels={'control': 'Control', 'treated': 'Treatment'},
))
results = i.dataset({'condition': ['control', 'treated'], 'value': [3, 6]})
bars = i.plot_spec(x=encoding.scale(), y=(0, 8))
bars.bars(results.column('condition'), results.column('value'), bar_colors=encoding)
bars.axes(y='Outcome')
bar_doc = i.document(width=100)
bar_doc.add('outcome', bars, min_height=50)
before = bar_doc.compile()
results.update(condition=['treated'], value=[6])
encoding.select(['treated'])
after = bar_doc.compile()
assert 'Control' in before.to_svg() and 'Control' not in after.to_svg()

Rendered from the code above.
Selection and table edits are separate operations: update both before compiling.
An encoding supplies stable colours, display labels and scale categories.
Unknown categories raise an error. For horizontal bars read top-to-bottom, use
y=encoding.scale(reverse=True) with orient='h'.
Source records and manifests¶
Source(citation, method=..., path=...) records the data origin. Supported
methods are measured, digitized, simulated and illustrative. If a path is
provided, the source file is hashed when the Source is constructed. Recreate
the Source when that file changes; this record is not itself a file watcher.
Compiled metadata includes dataset names, row counts, revisions, units and
content hashes, alongside source records and font hashes. Nested subfigures
retain their dataset provenance. The export bundle serializes it to
<name>-manifest.json. This records construction details, not source validity.
For a typed CSV table, use read_csv() as below. For custom parsing, use Python's
csv module or your existing data tools, then create the dataset. Use
inklet watch figure.py --watch data.csv to rerun the script when it changes.
See CLI reference for watch scope and file handling.
Typed CSV input¶
read_csv() returns the same live Dataset used
above and needs no pandas or NumPy dependency. This example creates a small local
file so the complete workflow can be run as written:
from pathlib import Path
csv_path = Path('example-response.csv')
csv_path.write_text('time,signal,group\n0,1,control\n1,3,control\n2,2,treated\n')
measurements = i.read_csv(
csv_path, types={'time': float, 'signal': float},
units={'time': 's', 'signal': 'mV'},
name='response table', citation='Illustrative CSV', method='simulated',
)
assert measurements.columns['group'] == ('control', 'control', 'treated')
assert measurements.source.sha256
types is required and maps selected headers to str, int or float.
Unspecified columns remain strings; this preserves identifiers such as 001.
Integer conversion retains Python's exact integer precision. Float conversion
rejects NaN and infinity. Empty numeric cells, malformed rows, duplicate or empty
headers and type names absent from the file raise errors. Rows are never silently
dropped, padded or filled. Empty string cells are allowed in string columns.
Input defaults to comma-delimited UTF-8 with optional BOM. Set delimiter and
encoding explicitly for other files; quoting and embedded newlines follow the
standard CSV reader. A blank physical row is rejected as a malformed record.
The Source records the absolute local path and SHA-256 of the bytes actually
parsed. It records input provenance, not validation of the supplied units or
measurements. Declare method='simulated' for simulated data.
Reading creates a snapshot; editing the file does not mutate an existing table.
Read it again to import changed bytes, or use Dataset.update() for explicit live
edits. The six-panel general plotting example uses this path
for machine-learning, engineering and business tables.
DataFrames for linked figures¶
inklet.selection.KeyedTable (4.3; the old inklet.experimental.selection
path still works) is a different object from Dataset. A Dataset is the live,
editable source of a static plot's columns. A KeyedTable is an immutable
snapshot of JSON scalar columns with a required string row key and a content
digest; linked views, SelectionState and figure projects use it to name rows
by ID across revisions. Build one from the same values with
KeyedTable(name, columns, key='id'), or from pandas and Polars with
KeyedTable.from_pandas() and KeyedTable.from_polars(). See
pandas and Polars inputs for the illustrated import,
revision and export workflow. The Dataset authoring API above keeps its
contract.