Chart
Data and space / clio.chart.v1
Explore a measured relationship, compare categories, or follow observations through time. Choose a preset or a guarded Vega-Lite specification for the scientific question.
When to use it
Explore a measured relationship, compare categories, or follow observations through time. Choose a preset or a guarded Vega-Lite specification for the scientific question.
What the agent supplies
Any chart over tabular rows: a preset or an Altair/Vega-Lite spec.
The agent chooses the content and binds it to the component. CLIO then renders the view and its available controls.
When this view uses the same dataset as another view, selection travels with each row. A person can find the same observation across the views.

What the agent receives
The exact output of load_skill, including resolved local definitions.
load_skill("a2ui-catalog-clio-workspace", file="catalog.json#/components/clio.chart.v1"){
"allOf": [
{
"$ref": "https://a2ui.org/specification/v0_9/common_types.json#/$defs/ComponentCommon"
},
{
"properties": {
"weight": {
"description": "Relative flex weight of this component inside a Row or Column. Only meaningful as a direct child of one.",
"type": "number"
}
},
"type": "object"
},
{
"dependentRequired": {
"dataQuery": [
"dataUri"
]
},
"properties": {
"colorField": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"component": {
"const": "clio.chart.v1"
},
"data": {
"description": "Inline rows (at most 10000) \u2014 this array rides the surface's own wire message, so it is capped for transfer size; for a larger or growing dataset use dataUri instead, unbounded and paged/downsampled by the viewer. A cell is a scalar OR a strictly-shaped GeoJSON Geometry object ($defs/GeoJsonGeometry), so a geoshape mark can draw real shapes from inline rows.",
"items": {
"additionalProperties": {
"anyOf": [
{
"type": [
"string",
"number",
"boolean",
"null"
]
},
{
"description": "A GeoJSON Geometry object (RFC 7946 \u00a73.1), strictly shaped: Point/MultiPoint/LineString/MultiLineString/Polygon/MultiPolygon carry coordinates; GeometryCollection carries geometries. Never a Feature/FeatureCollection, never a bare coordinate array.",
"oneOf": [
{
"additionalProperties": false,
"properties": {
"coordinates": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 2,
"type": "array"
},
"type": {
"const": "Point"
}
},
"required": [
"type",
"coordinates"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"coordinates": {
"items": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 2,
"type": "array"
},
"type": "array"
},
"type": {
"const": "MultiPoint"
}
},
"required": [
"type",
"coordinates"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"coordinates": {
"items": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 2,
"type": "array"
},
"minItems": 2,
"type": "array"
},
"type": {
"const": "LineString"
}
},
"required": [
"type",
"coordinates"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"coordinates": {
"items": {
"items": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 2,
"type": "array"
},
"minItems": 2,
"type": "array"
},
"type": "array"
},
"type": {
"const": "MultiLineString"
}
},
"required": [
"type",
"coordinates"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"coordinates": {
"items": {
"items": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 2,
"type": "array"
},
"minItems": 4,
"type": "array"
},
"type": "array"
},
"type": {
"const": "Polygon"
}
},
"required": [
"type",
"coordinates"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"coordinates": {
"items": {
"items": {
"items": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 2,
"type": "array"
},
"minItems": 4,
"type": "array"
},
"type": "array"
},
"type": "array"
},
"type": {
"const": "MultiPolygon"
}
},
"required": [
"type",
"coordinates"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"geometries": {
"items": {
"$ref": "#/$defs/GeoJsonGeometry",
"$comment": "recursive: #/$defs/GeoJsonGeometry is already being expanded on this path, left as a $ref"
},
"type": "array"
},
"type": {
"const": "GeometryCollection"
}
},
"required": [
"type",
"geometries"
],
"type": "object"
}
],
"type": "object"
}
]
},
"type": "object"
},
"maxItems": 10000,
"type": "array"
},
"dataQuery": {
"additionalProperties": false,
"description": "A server-side table query applied to dataUri: the table-query request body (POST /v1/artifacts/{id}/table-query) without format. The server runs filter, then aggregate, then downsample, then sort, then offset/limit. Omit columns to request every column of dataUri \u2014 except alongside aggregate, where columns is required, since aggregate's output columns (e.g. price_mean) don't exist in the source and so cannot be inferred. limit bounds the rows in ONE response (a transfer size), not the underlying data; offset pages through a larger result across several requests. When a chart or map query would otherwise exceed limit without an explicit downsample, the server instead samples evenly across the full range and reports that it did, rather than silently truncating to the first rows. limit's own maximum here is a generous safety ceiling, not the deployment's real per-response cap, which is server-configured. A viewer (e.g. an interactive data-table) may layer its own user-driven paging/filtering/sorting on top of this dataQuery without replacing it \u2014 the agent's own filter/aggregate/downsample intent still applies underneath. Shared verbatim by clio.chart.v1, clio.map.v1, and clio.data-table.v1.",
"if": {
"properties": {
"aggregate": {
"not": {
"type": "null"
}
}
},
"required": [
"aggregate"
]
},
"properties": {
"aggregate": {
"anyOf": [
{
"additionalProperties": false,
"description": "Group rows by groupBy (empty or omitted: one global group) and reduce; each metric becomes a result column named {column}_{fn}, which must not repeat a groupBy column.",
"properties": {
"groupBy": {
"items": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"maxItems": 64,
"type": "array",
"uniqueItems": true
},
"metrics": {
"items": {
"additionalProperties": false,
"properties": {
"column": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"fn": {
"enum": [
"mean",
"min",
"max",
"count",
"sum",
"median"
],
"type": "string"
}
},
"required": [
"column",
"fn"
],
"type": "object"
},
"maxItems": 64,
"minItems": 1,
"type": "array",
"uniqueItems": true
}
},
"required": [
"metrics"
],
"type": "object"
},
{
"type": "null"
}
]
},
"columns": {
"anyOf": [
{
"items": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"maxItems": 64,
"minItems": 1,
"type": "array",
"uniqueItems": true
},
{
"type": "null"
}
]
},
"downsample": {
"anyOf": [
{
"additionalProperties": false,
"description": "How the server thins rows before limit applies. none keeps every row; stride keeps evenly spaced rows (per entityColumn up to maxPerEntity when set, else overall up to limit); per_entity_lttb runs LTTB on (x, y) per entityColumn (the whole table is one series without it) and needs x and y.",
"if": {
"properties": {
"mode": {
"const": "per_entity_lttb"
}
},
"required": [
"mode"
]
},
"properties": {
"entityColumn": {
"anyOf": [
{
"maxLength": 128,
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
]
},
"maxPerEntity": {
"default": 500,
"maximum": 2000,
"minimum": 1,
"type": "integer"
},
"mode": {
"default": "none",
"enum": [
"none",
"stride",
"per_entity_lttb"
],
"type": "string"
},
"x": {
"anyOf": [
{
"maxLength": 128,
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
]
},
"y": {
"anyOf": [
{
"maxLength": 128,
"minLength": 1,
"type": "string"
},
{
"type": "null"
}
]
}
},
"then": {
"properties": {
"x": {
"not": {
"type": "null"
}
},
"y": {
"not": {
"type": "null"
}
}
},
"required": [
"x",
"y"
]
},
"type": "object"
},
{
"type": "null"
}
]
},
"filter": {
"anyOf": [
{
"items": {
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"op": {
"const": "eq"
}
}
},
"then": {
"properties": {
"value": {
"anyOf": [
{
"type": [
"string",
"number",
"boolean"
]
},
{
"additionalProperties": false,
"properties": {
"path": {
"pattern": "^/",
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
}
]
}
},
"required": [
"value"
]
}
},
{
"if": {
"properties": {
"op": {
"const": "in"
}
}
},
"then": {
"properties": {
"value": {
"anyOf": [
{
"items": {
"type": [
"string",
"number",
"boolean"
]
},
"maxItems": 10000,
"minItems": 1,
"type": "array"
},
{
"additionalProperties": false,
"properties": {
"path": {
"pattern": "^/",
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
}
]
}
},
"required": [
"value"
]
}
},
{
"if": {
"properties": {
"op": {
"const": "range"
}
}
},
"then": {
"properties": {
"value": {
"anyOf": [
{
"items": {
"type": [
"string",
"number",
"boolean",
"null"
]
},
"maxItems": 2,
"minItems": 2,
"type": "array"
},
{
"additionalProperties": false,
"properties": {
"path": {
"pattern": "^/",
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
}
]
}
},
"required": [
"value"
]
}
},
{
"if": {
"properties": {
"op": {
"const": "isnull"
}
}
},
"then": {
"properties": {
"value": {
"anyOf": [
{
"type": [
"boolean",
"null"
]
},
{
"additionalProperties": false,
"properties": {
"path": {
"pattern": "^/",
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
}
]
}
}
}
},
{
"if": {
"properties": {
"op": {
"const": "contains"
}
}
},
"then": {
"properties": {
"value": {
"anyOf": [
{
"minLength": 1,
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"path": {
"pattern": "^/",
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
}
]
}
},
"required": [
"value"
]
}
}
],
"description": "One predicate; all filter entries are AND-ed. eq: value is a non-null scalar. in: value is a non-empty list of scalars. range: value is [min, max], inclusive, either side may be null. isnull: value omitted or true matches nulls, false matches non-nulls. contains: value is a non-empty string, matched as a case-insensitive substring of a string column (a per-column text filter).",
"properties": {
"column": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"op": {
"enum": [
"eq",
"in",
"range",
"isnull",
"contains"
],
"type": "string"
},
"value": {}
},
"required": [
"column",
"op"
],
"type": "object"
},
"maxItems": 64,
"type": "array"
},
{
"type": "null"
}
]
},
"limit": {
"anyOf": [
{
"maximum": 50000,
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
]
},
"offset": {
"anyOf": [
{
"minimum": 0,
"type": "integer"
},
{
"type": "null"
}
]
},
"sort": {
"anyOf": [
{
"items": {
"additionalProperties": false,
"description": "One dataQuery.sort key; earlier entries in the list sort first.",
"properties": {
"column": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"desc": {
"default": false,
"type": "boolean"
}
},
"required": [
"column"
],
"type": "object"
},
"maxItems": 64,
"type": "array"
},
{
"type": "null"
}
]
}
},
"then": {
"required": [
"columns"
]
},
"type": "object"
},
"dataUri": {
"description": "A workspace file path or artifact:// reference; CLIO registers a path as an artifact before validation.",
"not": {
"pattern": "\\s"
},
"pattern": "^artifact://artifact_[A-Za-z0-9_-]+$",
"type": "string"
},
"entityField": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"facetField": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"height": {
"maximum": 2000,
"minimum": 80,
"type": "number"
},
"preset": {
"enum": [
"trajectories",
"heatmap",
"spectra",
"boxplot",
"scatter"
],
"type": "string"
},
"selection": {
"$ref": "https://a2ui.org/specification/v0_9/common_types.json#/$defs/DynamicValue",
"description": "Bind to /selection/<key>; the bound value is a SelectionState ({field, values[], source?}, see $defs/SelectionState). Components bound to the same path share one selection."
},
"selectionField": {
"maxLength": 128,
"minLength": 1,
"type": "string",
"description": "Column used for an explicit selection binding. Ordinary views of one dataUri link by __row without this property; trajectory and spectra points select exact rows while entityField groups each curve."
},
"selectionParam": {
"default": "sel",
"pattern": "^[A-Za-z_][A-Za-z0-9_]{0,63}$",
"type": "string"
},
"spec": {
"allOf": [
{
"description": "No `url` or `usermeta` key anywhere in a Vega-Lite spec, at any depth.",
"else": {
"if": {
"type": "array"
},
"then": {
"items": {
"$ref": "#/$defs/SpecNoForbiddenKeys",
"$comment": "recursive: #/$defs/SpecNoForbiddenKeys is already being expanded on this path, left as a $ref"
}
}
},
"if": {
"type": "object"
},
"then": {
"additionalProperties": {
"$ref": "#/$defs/SpecNoForbiddenKeys",
"$comment": "recursive: #/$defs/SpecNoForbiddenKeys is already being expanded on this path, left as a $ref"
},
"not": {
"anyOf": [
{
"required": [
"url"
]
},
{
"required": [
"usermeta"
]
}
]
}
}
},
{
"description": "Every `data` key anywhere in a Vega-Lite spec is absent or exactly {\"name\": \"source\"} \u2014 rows come from the component's data/dataUri, never the spec.",
"else": {
"if": {
"type": "array"
},
"then": {
"items": {
"$ref": "#/$defs/SpecDataNamedSource",
"$comment": "recursive: #/$defs/SpecDataNamedSource is already being expanded on this path, left as a $ref"
}
}
},
"if": {
"type": "object"
},
"then": {
"additionalProperties": {
"$ref": "#/$defs/SpecDataNamedSource",
"$comment": "recursive: #/$defs/SpecDataNamedSource is already being expanded on this path, left as a $ref"
},
"properties": {
"data": {
"const": {
"name": "source"
}
}
}
}
},
{
"description": "No `element` key inside any `bind` object (params[].bind.element) anywhere in a Vega-Lite spec, at any depth \u2014 it is a CSS selector that could mount a Vega input widget into any element on the host page, not just this chart.",
"else": {
"if": {
"type": "array"
},
"then": {
"items": {
"$ref": "#/$defs/SpecNoBindElement",
"$comment": "recursive: #/$defs/SpecNoBindElement is already being expanded on this path, left as a $ref"
}
}
},
"if": {
"type": "object"
},
"then": {
"additionalProperties": {
"$ref": "#/$defs/SpecNoBindElement",
"$comment": "recursive: #/$defs/SpecNoBindElement is already being expanded on this path, left as a $ref"
},
"properties": {
"bind": {
"allOf": [
{
"if": {
"type": "object"
},
"then": {
"not": {
"required": [
"element"
]
}
}
},
{
"$ref": "#/$defs/SpecNoBindElement",
"$comment": "recursive: #/$defs/SpecNoBindElement is already being expanded on this path, left as a $ref"
}
]
}
}
}
}
],
"description": "A Vega-Lite spec whose rows come from data/dataUri as the named dataset 'source' \u2014 the spec is chart configuration, never a data channel, and the dataset itself is unbounded via dataUri regardless of this cap. This schema already enforces the top-level key allowlist, no url/usermeta key at any depth, no bind.element at any depth (a CSS selector that could mount an input widget anywhere on the host page), and data only as {name: source} at any depth; the renderer additionally enforces the config-size (65536 bytes) and view-count (8) caps that keep the spec itself bounded and abuse-resistant, which JSON Schema cannot express. See a2ui/chart/guard_rules.json.",
"propertyNames": {
"enum": [
"$schema",
"mark",
"encoding",
"layer",
"facet",
"hconcat",
"vconcat",
"concat",
"repeat",
"spec",
"transform",
"params",
"width",
"height",
"title",
"resolve",
"config",
"autosize",
"description",
"columns",
"spacing",
"padding",
"align",
"bounds",
"center",
"projection",
"data"
]
},
"type": "object"
},
"title": {
"$ref": "https://a2ui.org/specification/v0_9/common_types.json#/$defs/DynamicString"
},
"xField": {
"maxLength": 128,
"minLength": 1,
"type": "string"
},
"xType": {
"enum": [
"temporal",
"quantitative",
"ordinal"
],
"type": "string"
},
"yField": {
"maxLength": 128,
"minLength": 1,
"type": "string"
}
},
"required": [
"component"
],
"type": "object"
},
{
"description": "Exactly one of spec or preset; preset fill fields need preset.",
"oneOf": [
{
"not": {
"anyOf": [
{
"required": [
"preset"
]
},
{
"required": [
"xField"
]
},
{
"required": [
"yField"
]
},
{
"required": [
"entityField"
]
},
{
"required": [
"colorField"
]
},
{
"required": [
"facetField"
]
},
{
"required": [
"xType"
]
}
]
},
"required": [
"spec"
]
},
{
"not": {
"required": [
"spec"
]
},
"required": [
"preset"
]
}
]
},
{
"description": "Exactly one of data or dataUri is required.",
"oneOf": [
{
"not": {
"required": [
"dataUri"
]
},
"required": [
"data"
]
},
{
"not": {
"required": [
"data"
]
},
"required": [
"dataUri"
]
}
]
},
{
"description": "Fields used by the trajectories preset.",
"if": {
"properties": {
"preset": {
"const": "trajectories"
}
},
"required": [
"preset"
]
},
"then": {
"required": [
"xField",
"yField",
"entityField"
]
}
},
{
"description": "Fields used by the heatmap preset.",
"if": {
"properties": {
"preset": {
"const": "heatmap"
}
},
"required": [
"preset"
]
},
"then": {
"required": [
"xField",
"yField",
"colorField",
"entityField"
]
}
},
{
"description": "Fields used by the spectra preset.",
"if": {
"properties": {
"preset": {
"const": "spectra"
}
},
"required": [
"preset"
]
},
"then": {
"required": [
"xField",
"yField",
"entityField"
]
}
},
{
"description": "Fields used by the boxplot preset.",
"if": {
"properties": {
"preset": {
"const": "boxplot"
}
},
"required": [
"preset"
]
},
"then": {
"not": {
"anyOf": [
{
"required": [
"facetField"
]
},
{
"required": [
"xType"
]
}
]
},
"required": [
"xField",
"yField",
"entityField"
]
}
},
{
"description": "Fields used by the scatter preset.",
"if": {
"properties": {
"preset": {
"const": "scatter"
}
},
"required": [
"preset"
]
},
"then": {
"required": [
"xField",
"yField",
"entityField"
]
}
}
],
"description": "Any chart over tabular rows: a preset or an Altair/Vega-Lite spec.",
"type": "object",
"unevaluatedProperties": false
}
Local refs still needing a separate load_skill(..., file=): catalog.json#/$defs/GeoJsonGeometry, catalog.json#/$defs/SpecDataNamedSource, catalog.json#/$defs/SpecNoBindElement, catalog.json#/$defs/SpecNoForbiddenKeys
Standard refs (not loadable here): https://a2ui.org/specification/v0_9/common_types.json#/$defs/ComponentCommon, https://a2ui.org/specification/v0_9/common_types.json#/$defs/DynamicString, https://a2ui.org/specification/v0_9/common_types.json#/$defs/DynamicValue