{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://casoon.github.io/chartlet/schema/v1.json",
"title": "chartlet v1 alpha specification",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "type", "title"],
"properties": {
"schemaVersion": { "const": 1 },
"type": {
"enum": ["bar", "line", "time", "topicmap", "atlas", "stripes", "calendar", "rangebar", "multiples"]
},
"orientation": { "enum": ["vertical", "horizontal"], "default": "vertical" },
"title": { "type": "string", "minLength": 1, "maxLength": 200 },
"description": { "type": "string", "minLength": 1, "maxLength": 1000 },
"source": { "type": "string", "minLength": 1, "maxLength": 300 },
"theme": { "enum": ["light", "dark"], "default": "light" },
"locale": {
"description": "Language of the texts chartlet generates (description, legend additions, tooltips, HTML figure and table) and of the number format.",
"enum": ["en", "de"],
"default": "en"
},
"categoryAxis": { "$ref": "#/$defs/categoryAxis" },
"valueAxis": { "$ref": "#/$defs/valueAxis" },
"timeAxis": { "$ref": "#/$defs/timeAxis" },
"panes": {
"description": "Time charts are drawn from 1 to 4 panes stacked on one time axis, small multiples from 2 to 12 titled panes.",
"type": "array",
"minItems": 1,
"maxItems": 12,
"items": { "$ref": "#/$defs/pane" }
},
"columns": {
"description": "Grid columns of a type: \"multiples\" chart; defaults to up to three.",
"type": "integer",
"minimum": 1,
"maximum": 6
},
"stripes": {
"description": "The yearly values of a type: \"stripes\" chart.",
"$ref": "#/$defs/stripes"
},
"calendar": {
"description": "The daily values of a type: \"calendar\" chart.",
"$ref": "#/$defs/calendar"
},
"ranges": {
"description": "The spans of a type: \"rangebar\" chart, one per category.",
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": { "$ref": "#/$defs/range" }
},
"topicmap": {
"description": "Topic areas of a type: \"topicmap\" chart.",
"$ref": "#/$defs/topicMap"
},
"atlas": {
"description": "The knowledge landscape of a type: \"atlas\" chart.",
"$ref": "#/$defs/atlas"
},
"width": { "type": "integer", "minimum": 320, "maximum": 2400, "default": 800 },
"height": { "type": "integer", "minimum": 240, "maximum": 1600, "default": 450 },
"mobile": {
"description": "A second layout for narrow containers. The HTML profile shows it below the breakpoint by a container query; the SVG profile renders it with the variant option.",
"type": "object",
"additionalProperties": false,
"required": ["width"],
"properties": {
"width": { "type": "integer", "minimum": 280, "maximum": 600 },
"height": { "type": "integer", "minimum": 240, "maximum": 1600, "default": 360 },
"breakpoint": {
"description": "Container width in CSS pixels below which the mobile variant is shown.",
"type": "integer",
"minimum": 320,
"maximum": 1600,
"default": 640
}
}
},
"showValues": { "type": "boolean", "default": true },
"showTitle": {
"description": "false leaves the drawn title out of a time chart; it stays the accessible name. Time charts only.",
"type": "boolean",
"default": true
},
"data": {
"description": "Single-series data. Use either data or categories with series.",
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["label", "value"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 200 },
"value": { "$ref": "#/$defs/value" }
}
}
},
"categories": {
"description": "Unique category labels shared by all series.",
"type": "array",
"minItems": 1,
"maxItems": 100,
"uniqueItems": true,
"items": { "type": "string", "minLength": 1, "maxLength": 200 }
},
"series": {
"description": "Named series with one value per category; null marks a missing value.",
"type": "array",
"minItems": 1,
"maxItems": 4,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name", "values"],
"properties": {
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
"values": { "type": "array", "items": { "$ref": "#/$defs/value" } }
}
}
},
"zoomSteps": {
"description": "Optional pre-computed zoom variants, selectable in the HTML profile. A bar or line chart names categories by index, a time chart names a window by timestamps.",
"type": "array",
"minItems": 2,
"maxItems": 4,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["label", "from", "to"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 40 },
"from": {
"description": "First category index, or on a time chart the first timestamp of the window.",
"anyOf": [{ "type": "integer", "minimum": 0 }, { "$ref": "#/$defs/timeValue" }]
},
"to": {
"description": "Last category index, or on a time chart the last timestamp of the window.",
"anyOf": [{ "type": "integer", "minimum": 0 }, { "$ref": "#/$defs/timeValue" }]
}
}
}
}
},
"allOf": [
{
"oneOf": [
{
"required": ["data"],
"not": { "anyOf": [{ "required": ["categories"] }, { "required": ["series"] }] }
},
{
"required": ["categories", "series"],
"not": { "required": ["data"] }
},
{
"required": ["panes"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] }
]
}
},
{
"required": ["topicmap"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] },
{ "required": ["panes"] }
]
}
},
{
"required": ["atlas"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] },
{ "required": ["panes"] }
]
}
},
{
"required": ["stripes"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] },
{ "required": ["panes"] }
]
}
},
{
"required": ["calendar"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] },
{ "required": ["panes"] }
]
}
},
{
"required": ["ranges"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] },
{ "required": ["panes"] }
]
}
}
]
},
{
"if": { "properties": { "type": { "const": "stripes" } } },
"then": { "required": ["stripes"] },
"else": { "not": { "required": ["stripes"] } }
},
{
"if": { "properties": { "type": { "const": "calendar" } } },
"then": { "required": ["calendar"] },
"else": { "not": { "required": ["calendar"] } }
},
{
"if": { "properties": { "type": { "const": "rangebar" } } },
"then": { "required": ["ranges"] },
"else": { "not": { "required": ["ranges"] } }
},
{
"if": { "properties": { "type": { "const": "multiples" } } },
"then": {
"required": ["panes"],
"properties": { "panes": { "minItems": 2 } },
"not": {
"anyOf": [
{ "required": ["orientation"] },
{ "required": ["categoryAxis"] },
{ "required": ["zoomSteps"] },
{ "required": ["topicmap"] },
{ "required": ["atlas"] }
]
}
},
"else": { "not": { "required": ["columns"] } }
},
{
"if": { "properties": { "type": { "const": "line" } } },
"then": { "not": { "anyOf": [{ "required": ["orientation"] }, { "required": ["series"] }] } }
},
{
"if": { "properties": { "type": { "const": "time" } } },
"then": {
"required": ["panes"],
"properties": { "panes": { "maxItems": 4 } },
"not": {
"anyOf": [
{ "required": ["orientation"] },
{ "required": ["categoryAxis"] },
{ "required": ["valueAxis"] },
{ "required": ["topicmap"] }
]
}
}
},
{
"if": { "properties": { "type": { "enum": ["bar", "line"] } } },
"then": {
"properties": {
"zoomSteps": {
"items": {
"properties": {
"from": { "type": "integer", "minimum": 0 },
"to": { "type": "integer", "minimum": 0 }
}
}
}
},
"not": {
"anyOf": [
{ "required": ["panes"] },
{ "required": ["timeAxis"] },
{ "required": ["topicmap"] }
]
}
}
},
{
"if": { "properties": { "type": { "const": "topicmap" } } },
"then": {
"required": ["topicmap"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["categories"] },
{ "required": ["series"] },
{ "required": ["zoomSteps"] },
{ "required": ["panes"] },
{ "required": ["orientation"] },
{ "required": ["categoryAxis"] },
{ "required": ["valueAxis"] },
{ "required": ["timeAxis"] }
]
}
}
}
],
"$defs": {
"value": {
"anyOf": [
{ "type": "null" },
{ "const": 0 },
{ "type": "number", "minimum": 1e-100, "maximum": 1e100 },
{ "type": "number", "minimum": -1e100, "maximum": -1e-100 }
]
},
"categoryAxis": {
"type": "object",
"additionalProperties": false,
"properties": {
"title": { "type": "string", "minLength": 1, "maxLength": 100 }
}
},
"valueAxis": {
"type": "object",
"additionalProperties": false,
"properties": {
"title": { "type": "string", "minLength": 1, "maxLength": 100 },
"format": { "enum": ["number", "percent"], "default": "number" },
"decimals": {
"description": "Fixed decimal places for values in labels, tooltips, description and data table; axis ticks follow the tick step.",
"type": "integer",
"minimum": 0,
"maximum": 6
}
}
},
"timeAxis": {
"description": "The time axis of a type: \"time\" chart: Unix seconds or ISO 8601 timestamps.",
"type": "object",
"additionalProperties": false,
"properties": {
"timezone": {
"description": "UTC or a fixed offset such as +02:00. Timestamps without their own offset are read as wall-clock time in this zone.",
"type": "string",
"pattern": "^(UTC|utc|Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$",
"default": "UTC"
},
"gaps": { "enum": ["show", "collapse"], "default": "show" },
"title": { "type": "string", "minLength": 1, "maxLength": 100 }
}
},
"timeValue": {
"description": "Unix seconds between 1700-01-01 and 2200-01-01, an ISO 8601 date such as 2026-03-01 or 2026-03-01T12:00:00Z, or a year such as 1850 (January 1st).",
"anyOf": [
{ "type": "integer", "minimum": -8520336000, "maximum": 7258118399 },
{ "type": "string", "pattern": "^[0-9]{4}$" },
{ "type": "string", "minLength": 10, "maxLength": 25 }
]
},
"stripes": {
"description": "Warming stripes: one value per consecutive year from firstYear on; null leaves a year empty. The diverging scale has eight equal steps on either side of reference; min and max default to the largest distance from reference, and values beyond them take the outermost step.",
"type": "object",
"additionalProperties": false,
"required": ["firstYear", "values"],
"properties": {
"firstYear": { "type": "integer", "minimum": 1, "maximum": 9999 },
"values": {
"type": "array",
"minItems": 1,
"maxItems": 500,
"items": { "$ref": "#/$defs/value" }
},
"reference": { "type": "number", "default": 0 },
"min": { "type": "number" },
"max": { "type": "number" },
"yearLabels": {
"description": "Label the first and the last year under the stripes.",
"type": "boolean",
"default": true
}
}
},
"calendar": {
"description": "A calendar heatmap of one year. A day that is absent or null stays an empty cell. reference, min and max work as for stripes.",
"type": "object",
"additionalProperties": false,
"required": ["year", "days"],
"properties": {
"year": { "type": "integer", "minimum": 1700, "maximum": 2199 },
"layout": {
"description": "months: twelve rows of up to 31 days; weeks: seven weekday rows, one column per week.",
"enum": ["months", "weeks"],
"default": "months"
},
"days": {
"type": "array",
"minItems": 1,
"maxItems": 366,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["date", "value"],
"properties": {
"date": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
"value": { "$ref": "#/$defs/value" }
}
}
},
"reference": { "type": "number", "default": 0 },
"min": { "type": "number" },
"max": { "type": "number" }
}
},
"range": {
"description": "One span of a rangebar chart; mid must lie between low and high.",
"type": "object",
"additionalProperties": false,
"required": ["label", "low", "high"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 200 },
"low": { "type": "number" },
"high": { "type": "number" },
"mid": { "type": "number" },
"modeled": { "type": "boolean", "default": false }
}
},
"pane": {
"type": "object",
"additionalProperties": false,
"properties": {
"title": {
"description": "Heading of a small-multiples panel; required there, not allowed in a time chart, whose panes are named by their valueAxis title.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"heightRatio": {
"description": "Time charts: the pane's share of the plot height against the other panes. Small multiples keep the default.",
"type": "integer",
"minimum": 1,
"maximum": 10,
"default": 1
},
"valueAxis": { "$ref": "#/$defs/valueAxis" },
"layers": {
"description": "Up to 6 data layers — on a time chart at most 4 line and area layers of the whole chart without their own color — and up to 6 annotation layers (zones, reference lines and point markers together).",
"type": "array",
"minItems": 1,
"maxItems": 12,
"items": { "$ref": "#/$defs/layer" }
}
}
},
"layer": {
"description": "One layer of a pane. Which fields apply depends on mark.",
"type": "object",
"additionalProperties": false,
"required": ["mark"],
"properties": {
"mark": {
"description": "line and area draw data; ohlc draws candlesticks (time charts only); band is a zone; annotation is a reference line or, with both time and value, a point marker.",
"enum": ["line", "area", "ohlc", "band", "annotation"]
},
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
"color": {
"description": "#rgb, #rrggbb, #rrggbbaa or var(--name). Anything else is reported and replaced by the neutral tone.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"points": {
"description": "Observations of a line or area layer.",
"type": "array",
"minItems": 1,
"maxItems": 2000,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["time", "value"],
"properties": {
"time": { "$ref": "#/$defs/timeValue" },
"value": {
"description": "null marks a missing observation: the line, its area and its band break there.",
"$ref": "#/$defs/value"
},
"lower": {
"description": "Lower edge of the uncertainty band; give lower and upper on every point of a layer that has a value, or on none. A point whose value is null has no band.",
"type": "number"
},
"upper": { "description": "Upper edge of the uncertainty band.", "type": "number" }
}
}
},
"data": {
"description": "Candlesticks of an ohlc layer, with increasing time; low at or below open and close, high at or above them.",
"type": "array",
"minItems": 2,
"maxItems": 2000,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["time", "open", "high", "low", "close"],
"properties": {
"time": { "$ref": "#/$defs/timeValue" },
"open": { "type": "number" },
"high": { "type": "number" },
"low": { "type": "number" },
"close": { "type": "number" }
}
}
},
"from": {
"description": "Start of a zone; missing, the zone begins at the plot's left edge.",
"$ref": "#/$defs/timeValue"
},
"to": {
"description": "End of a zone, after from; missing, the zone ends at the plot's right edge.",
"$ref": "#/$defs/timeValue"
},
"top": {
"description": "Upper edge of a zone, above bottom; missing, the zone reaches the top of the plot.",
"type": "number"
},
"bottom": {
"description": "Lower edge of a zone; missing, the zone reaches the bottom of the plot.",
"type": "number"
},
"time": {
"description": "Time of a vertical reference line or of a point marker.",
"$ref": "#/$defs/timeValue"
},
"value": {
"description": "Value of a horizontal reference line or of a point marker.",
"type": "number"
},
"label": {
"description": "Text of a reference line, point marker or zone; required there.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"shape": {
"description": "Symbol of a point marker; circle by default. Only a point marker takes one.",
"enum": ["triangle-up", "triangle-down", "square", "circle", "diamond"]
},
"modeled": {
"description": "A line from a model: drawn dashed unless dash says otherwise, its band hatched, and named modeled in legend and description.",
"type": "boolean",
"default": false
},
"stroke": {
"description": "Line weight; thin sets a line back, for example single years under their mean, bold brings one forward.",
"enum": ["regular", "thin", "bold"],
"default": "regular"
},
"dash": {
"description": "Line pattern. Defaults to dashed for a modeled line and solid for any other.",
"enum": ["solid", "dashed", "dotted"]
}
},
"allOf": [
{
"if": { "properties": { "mark": { "enum": ["line", "area"] } }, "required": ["mark"] },
"then": {
"required": ["points"],
"not": {
"anyOf": [
{ "required": ["data"] },
{ "required": ["from"] },
{ "required": ["to"] },
{ "required": ["top"] },
{ "required": ["bottom"] },
{ "required": ["time"] },
{ "required": ["value"] },
{ "required": ["label"] },
{ "required": ["shape"] }
]
}
}
},
{
"if": { "properties": { "mark": { "const": "ohlc" } }, "required": ["mark"] },
"then": {
"description": "Candlesticks: rising candles are hollow, falling ones filled, in --chartlet-rise and --chartlet-fall.",
"required": ["data"],
"not": {
"anyOf": [
{ "required": ["points"] },
{ "required": ["color"] },
{ "required": ["from"] },
{ "required": ["to"] },
{ "required": ["top"] },
{ "required": ["bottom"] },
{ "required": ["time"] },
{ "required": ["value"] },
{ "required": ["label"] },
{ "required": ["shape"] },
{ "required": ["modeled"] },
{ "required": ["stroke"] },
{ "required": ["dash"] }
]
}
}
},
{
"if": { "properties": { "mark": { "const": "annotation" } }, "required": ["mark"] },
"then": {
"description": "A reference line: value draws a horizontal one, time a vertical one; both draw a point marker, which alone takes a shape. label is required.",
"required": ["label"],
"anyOf": [{ "required": ["value"] }, { "required": ["time"] }],
"if": { "not": { "required": ["time", "value"] } },
"then": { "not": { "required": ["shape"] } },
"not": {
"anyOf": [
{ "required": ["name"] },
{ "required": ["points"] },
{ "required": ["data"] },
{ "required": ["from"] },
{ "required": ["to"] },
{ "required": ["top"] },
{ "required": ["bottom"] },
{ "required": ["modeled"] },
{ "required": ["stroke"] },
{ "required": ["dash"] }
]
}
}
},
{
"if": { "properties": { "mark": { "const": "band" } }, "required": ["mark"] },
"then": {
"description": "A zone between bottom and top, from and to, or both; a missing edge is the plot's edge. label is required.",
"required": ["label"],
"anyOf": [{ "required": ["bottom", "top"] }, { "required": ["from", "to"] }],
"not": {
"anyOf": [
{ "required": ["name"] },
{ "required": ["points"] },
{ "required": ["data"] },
{ "required": ["time"] },
{ "required": ["value"] },
{ "required": ["shape"] },
{ "required": ["modeled"] },
{ "required": ["stroke"] },
{ "required": ["dash"] }
]
}
}
}
]
},
"topic": {
"description": "One topic or island of a type: \"topicmap\" chart.",
"type": "object",
"additionalProperties": false,
"required": ["label", "value"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 200 },
"value": { "type": "number", "minimum": 1, "maximum": 1e100 },
"points": {
"description": "Number of paths through this topic; drawn as points inside its area.",
"type": "integer",
"minimum": 0,
"default": 0
},
"tooltip": { "type": "string", "minLength": 1, "maxLength": 300 }
}
},
"topicLink": {
"description": "A neighborhood between two topics or islands, drawn as a route.",
"type": "object",
"additionalProperties": false,
"required": ["from", "to"],
"properties": {
"from": { "type": "string", "minLength": 1 },
"to": { "type": "string", "minLength": 1 },
"weight": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 }
}
},
"cartouche": {
"description": "The legend box printed on the map.",
"type": "object",
"additionalProperties": false,
"required": ["heading", "meta"],
"properties": {
"heading": { "type": "string", "minLength": 1, "maxLength": 100 },
"meta": { "type": "string", "minLength": 1, "maxLength": 200 },
"corner": {
"enum": ["bottomRight", "bottomLeft", "topRight", "topLeft"],
"default": "bottomRight"
}
}
},
"atlas": {
"description": "Realms of neighbouring regions, holding places. Where a region lies says as much as how large it is.",
"type": "object",
"additionalProperties": false,
"required": ["realms"],
"properties": {
"realms": {
"type": "array",
"minItems": 1,
"maxItems": 8,
"items": { "$ref": "#/$defs/realm" }
},
"links": {
"description": "Kinship between two regions, named by label and valid across realm borders.",
"type": "array",
"items": { "$ref": "#/$defs/topicLink" }
},
"seed": { "type": "integer", "minimum": 0, "default": 0 },
"areaDamping": {
"description": "The exponent a region's area follows: 1 is proportional to its value, 0.5 its square root.",
"type": "number",
"minimum": 0.2,
"maximum": 1,
"default": 0.5
},
"contours": {
"description": "Contour lines from the density of places.",
"type": "boolean",
"default": true
}
}
},
"realm": {
"type": "object",
"additionalProperties": false,
"required": ["label", "regions"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 200 },
"regions": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/region" }
},
"tooltip": { "type": "string", "minLength": 1, "maxLength": 300 }
}
},
"region": {
"type": "object",
"additionalProperties": false,
"required": ["label", "value"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 200 },
"value": { "type": "number", "minimum": 1 },
"places": {
"type": "array",
"items": { "$ref": "#/$defs/place" }
},
"tooltip": { "type": "string", "minLength": 1, "maxLength": 300 }
}
},
"place": {
"type": "object",
"additionalProperties": false,
"required": ["label"],
"properties": {
"label": { "type": "string", "minLength": 1, "maxLength": 200 },
"weight": {
"description": "How prominent the point is. 1 is an ordinary place.",
"type": "number",
"minimum": 0.25,
"maximum": 4,
"default": 1
},
"tooltip": { "type": "string", "minLength": 1, "maxLength": 300 }
}
},
"topicMap": {
"type": "object",
"additionalProperties": false,
"required": ["topics"],
"properties": {
"topics": {
"type": "array",
"minItems": 1,
"maxItems": 40,
"items": { "$ref": "#/$defs/topic" }
},
"links": {
"type": "array",
"items": { "$ref": "#/$defs/topicLink" }
},
"islands": {
"description": "Fringe topics, drawn as small fixed-size islands rather than area-proportional landmasses.",
"type": "array",
"maxItems": 6,
"items": { "$ref": "#/$defs/topic" }
},
"seed": { "type": "integer", "minimum": 0, "default": 0 },
"cartouche": { "$ref": "#/$defs/cartouche" },
"graticule": { "type": "boolean", "default": true },
"compass": { "type": "boolean", "default": true },
"depthBands": {
"description": "Number of depth-line rings drawn around each coastline.",
"type": "integer",
"minimum": 0,
"maximum": 3,
"default": 2
}
}
}
}
}