# Plot Document Format

A plot document is a JSON object that describes one Graph Paper plot: its
formulas, its sliders, its draggable points and its axes. Encode it in a link
and Graph Paper opens it as a document that the reader can edit. This page
lists every field.

This page is for programs and AI agents that write documents. A program that
wants to give a person a plot starts at [For Agents](/for-agents/en/): the
fastest way is to send the document with
`POST https://graph-paper.io/plot-link`, or with
`GET https://graph-paper.io/plot-link?doc=<percent-encoded JSON>` from a tool
that can only fetch an address; the answer has the link, or the errors. For
what to write inside a formula, see the [Plotting Guide](/plotting-guide/en/).

**Every formula is LaTeX.** In a JSON string, write `"\\sin(x)"`, not
`"sin(x)"`, and `"x^{2}"`, not `"x**2"`. The validator refuses a function
name written as plain text (`exp(…)`, `sqrt(…)`) and says what to write. A
number in scientific notation such as `2.312e-05` is accepted and read as the
number, and Graph Paper shows it as 2.312 × 10⁻⁵, the form a person who types
it gets. Some literals are shown as written (`2.312e − 05`, which a person can
read as the constant e and a subtraction), and the validator gives a warning
for them: a literal with a space inside it, one followed by `^`, `_` or a
prime, one in a subscript or in a bare `{…}` group, one directly after `}` or
after a command such as `\frac`, and one whose mantissa starts with a point
(`.5e3`). For those, write a plain decimal (`0.00002312`) or
`a\cdot 10^{n}` (`"2.312\\cdot 10^{-5}"` in JSON). A point with no digits
after it (`1.e5`) or a point in the exponent (`1e3.5`) is not a number that
Graph Paper can read, and the validator refuses it.

A program should read the Markdown version of this page, at
<https://graph-paper.io/plot-document-format/en/index.md>, because a summary of
the HTML page can lose the tables. Every guide page has a Markdown version: add
`index.md` to its URL, or send `Accept: text/markdown`.
<https://graph-paper.io/llms-full.txt> is one file that holds the English text
of this page, [For Agents](/for-agents/en/), the Plotting Guide and Tips and
Tricks.

## Contents

- [A Minimal Document](#a-minimal-document)
- [Rules and Limits](#rules-and-limits)
- [Top-Level Fields](#top-level-fields)
- [Variables](#variables)
- [Points](#points)
- [A Point That Moves](#a-point-that-moves)
- [Definitions](#definitions)
- [Actions](#actions)
- [Series](#series)
- [2D Series Types](#2d-series-types)
- [3D Series Types](#3d-series-types)
- [Data Without a Formula](#data-without-a-formula)
- [Derived Series](#derived-series)
- [Markers](#markers)
- [A Segment Between Two Points](#a-segment-between-two-points)
- [Axes and Scene](#axes-and-scene)
- [Patterns](#patterns)
- [Complete Examples](#complete-examples)
- [A Simulation With a Timer](#a-simulation-with-a-timer)
- [Check a Document](#check-a-document)
- [Learn From the Showcase](#learn-from-the-showcase)
- [Notebook Documents](#notebook-documents)

## A Minimal Document

One title and one series:

```json
{
  "title": "Sine wave",
  "series": [{ "type": "line", "fn": "\\sin(x)" }]
}
```

To open it, encode the JSON as UTF-8, then as base64url (RFC 4648 §5, no
padding), and add it to `https://graph-paper.io/new#doc=`. The
[complete examples](#complete-examples) below show the full links. A program
that cannot compute base64url can write the JSON, percent-encoded, after
`https://graph-paper.io/new#json=`. A link has one form only. The service `https://graph-paper.io/plot-link` checks a document and returns
its link, with no account. See [Check a Document](#check-a-document).

## Rules and Limits

- The document is at most 65,536 bytes of UTF-8 JSON.
- `title` is plain text, at most 200 characters.
- A `note` is Markdown with inline `$…$` math, at most 2,000 characters.
- A document holds at most 100 `series`, 100 `variables`, 100 `points`, 200
  `definitions` and 50 `actions`.
- Every number must be finite. A number too large for a double, such as
  `1e999`, makes the document invalid.
- A `fn` must not be empty or hold only spaces.
- Graph Paper ignores a key it does not know, at the top level and on a row. It
  does not reject the document. The [link service](#check-a-document) gives a
  warning for each key that it ignores. When the key differs from a field only
  in letter case, the warning names the field.
- The keys `__proto__`, `constructor` and `prototype` are removed, at every
  depth.
- A document nested more than 32 levels deep is refused. The document object
  is level 1, so `{ "series": [ { … } ] }` is 3 levels deep.
- A field that is internal to Graph Paper, such as `fnTemplate` or
  `maskPredicate` on a series, is not part of the format. It is ignored, with
  a warning.
- In `stage`, `domain`, `stage3d` and `domain3d`, an unknown key at the top
  level of the object is ignored, with a warning. The values below the top
  level are kept as given.
- Every series type accepts `colorBar`: the color bar of a series with a
  colormap color and `"legend": true`. Its keys are `anchor` (`"top-left"`,
  `"top"`, `"top-right"`, `"right"`, `"bottom-right"`, `"bottom"`,
  `"bottom-left"` or `"left"`), `placement` (`"inside"` or `"outside"`),
  `label` (a string of at most 2,000 characters) and `visible` (a boolean). A
  value of the wrong type is an error.
- When an error or a warning repeats a key from the document, the key is cut
  to 64 characters and `…` is added.
- A series gives a formula in `fn` or literal data, not both. When a series
  has a `fn`, its literal data (`x`, `y`, `z`, `theta`, `r`, `polygons`,
  `fillColors` or `items`) is ignored, with a warning.
- Literal data must be complete: each array of data has at least one value,
  two arrays that pair their values (`x` and `y`, `theta` and `r`) have the
  same length, and a grid has the same number of values in each row. A value
  that is wrong is an error. See
  [Data Without a Formula](#data-without-a-formula).
- The link service lists at most 100 errors and 100 warnings. When there are
  more, one last item gives the number that is not listed.
- Formulas are LaTeX strings. In JSON, write each backslash twice: `"\\sin(x)"`
  is the LaTeX `\sin(x)`.
- Variable values and bounds are LaTeX strings too (`"0.3"`, `"2\\pi"`), so
  that exact values stay exact. Axis ranges and domains are JSON numbers.
- Where a variable, a point or an action expects a LaTeX string (`value`,
  `min`, `max`, `step`, `interval`), a finite JSON number is also accepted:
  `"value": 0.3` is the same as `"value": "0.3"`.

## Top-Level Fields

| Field         | Type   | Required | Meaning                                                                                                               |
| ------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `title`       | string | yes      | The document title, plain text.                                                                                       |
| `series`      | array  | yes      | The plotted rows, at least one. See [Series](#series).                                                                |
| `variables`   | array  | no       | Sliders. See [Variables](#variables).                                                                                 |
| `points`      | array  | no       | Points the reader drags on the plot. See [Points](#points).                                                           |
| `definitions` | array  | no       | Named values and functions that other rows use. See [Definitions](#definitions).                                      |
| `actions`     | array  | no       | Rows that change variables, once or on a timer. See [Actions](#actions).                                              |
| `markers`     | array  | no       | Annotations on the plot: labels, segments, rules, bands. See [Markers](#markers).                                    |
| `domain`      | object | no       | 2D axis ranges, aspect ratio and coordinate system. See [Axes and Scene](#axes-and-scene).                            |
| `stage`       | object | no       | 2D appearance: grid, axes, frame.                                                                                     |
| `domain3d`    | object | no       | 3D axis ranges and aspect.                                                                                            |
| `stage3d`     | object | no       | 3D scene: environment, lighting, camera, projection.                                                                  |
| `preset`      | object | no       | The document appearance: `{ "base": "basel" }`. Bases: `basel`, `vellum`, `academic`, `slides`.                       |
| `category`    | string | no       | `"2d"`, `"3d"` or `"data"`. When it is absent, Graph Paper finds it from the series types.                            |

The document opens with its rows in this order: variables, points,
definitions, actions, then series. A row can use a name that a row above it
defines.

The type of the first series decides whether the document is a 2D or a 3D plot.
Do not mix 2D and 3D series in one document.

## Variables

A variable is a slider. Each row of `variables` is an object:

| Field      | Type    | Required | Meaning                                                                                    |
| ---------- | ------- | -------- | ------------------------------------------------------------------------------------------ |
| `name`     | string  | yes      | The name, as formulas use it: `"a"`, `"c_1"`, `"omega"` or `"\\omega"`; see the naming rules below. |
| `value`    | string  | yes      | The start value, LaTeX: `"0.3"`, `"\\frac{\\pi}{2}"`.                                      |
| `domain`   | object  | yes      | The values the slider allows. See below.                                                   |
| `playback` | object  | no       | How the variable animates: `{ "mode": "bounce", "direction": "forward", "duration": 5000 }`. When absent: `"bounce"`, `"forward"`, 4000 ms. |
| `autoplay` | boolean | no       | `true` starts the animation when the document opens. When absent or `false`, the variable is at rest. |
| `note`     | string  | no       | A text shown above the row.                                                                |

`domain` takes one of two forms:

- A range: `{ "type": "range", "min": "0", "max": "2\\pi" }`, with an optional
  `"step"` (LaTeX).
- A list of values: `{ "type": "values", "values": ["1", "3", "5"] }`.

`playback.mode` is `"once"`, `"loop"`, `"bounce"` or `"all-at-once"`.
`"all-at-once"` does not animate: it draws each series once for each value of a
`values` domain. `direction` is `"forward"` or `"reverse"`. `duration` is the
time of one pass, in milliseconds.

Do not use `x`, `y` or `z` as a variable name: these names are the axes of the
plot. Do not use `e`, `i` or `pi` either: formulas read them as constants.

`t`, `u`, `v`, `r`, `theta` and `phi` are the names that the plot gives to the
parameter of a parametric curve (`t`), of a parametric surface (`u`, `v`), to a
polar angle (`theta`, `phi`) and to the polar radius (`r`). A variable can have
one of these names. Then every formula of the document that uses the name uses
the value of the variable. For example, with a variable `t`, the parametric
series `(\cos(t), \sin(t))` draws no curve, because all its points use the same
value of `t`. There is one exception: on the left side of a polar equation, `r`
is the radius. With a variable `r`, the formula `r = 2\cos(\theta)` is still a
polar curve whose radius is `r`.

A name is one letter (`"M"`, `"s"`) or the name of a Greek letter
(`"omega"`, `"epsilon"`), with an optional subscript (`"c_1"`). A subscript in
braces follows one letter only (`"x_{max}"`); after a Greek name, write
`"theta_0"` or `"\\theta_{0}"`, not `"theta_{0}"`, which the validator
refuses. Some names are other symbols in a formula when they are written
plain: name `"\\zeta"`, `"\\Pi"`, the variants (`"\\varphi"`,
`"\\vartheta"`, …) and `"\\hbar"` as LaTeX, so that the slider drives the
formula. In a formula, `\delta` with a subscript is the Kronecker delta and
`\mu_0` is a constant: name a variable `"d_1"` rather than `"delta_1"`, and
write `\mu_{0}`, with braces, for a variable `"mu_0"`. A formula
reads a name of several Latin letters, such as `mass`, as a product of
letters. In a formula, write a Greek letter with its LaTeX command: the variable
`"epsilon"` is `\epsilon` in a formula, and `"\\epsilon"` in the JSON of
`fn`.

The slider row shows a Greek name as its letter: the variable `"omega"` shows as
$\omega$. Name a Greek letter by its plain name (`"epsilon"`, `"rho"`), not by a
variant name (`"varepsilon"`), which Graph Paper shows as an upright word. A
capital that looks like a Latin letter (`"Alpha"`, `"Mu"`) also shows as an
upright word.

## Points

A point is a pair of variables that the reader changes by dragging a marker on
the plot. Each row of `points` is an object:

| Field  | Type   | Required | Meaning                                                                      |
| ------ | ------ | -------- | ---------------------------------------------------------------------------- |
| `x`    | object | yes      | The first coordinate: `{ "name": "c_x", "value": "0.5" }`.                   |
| `y`    | object | yes      | The second coordinate: `{ "name": "c_y", "value": "1" }`.                    |
| `drag` | string | no       | `"x"` or `"y"` to allow only that direction, `"none"` for no drag. By default the point moves freely. |
| `note` | string | no       | A text shown above the row.                                                  |

Formulas use the two coordinate names as ordinary variables. Points are for 2D
documents. An action can also change the two coordinates.

## A Point That Moves

A `scatter` series whose `fn` is a point formula draws a point that moves. The
formula can use variables and definitions:

```json
{ "type": "scatter", "fn": "(L\\sin(a), -L\\cos(a))", "color": "#c62828", "marker": { "size": 14 } }
```

When a slider, a drag or an action changes `L` or `a`, Graph Paper draws the
point again at its new position. The
[pendulum](#a-simulation-with-a-timer) uses this row for its bob.

A point in `points` is different. It is an input: the reader drags it, and the
drag sets two variables that other rows read. A `scatter` point is a result:
its formula sets its position. Use `points` for a value that the reader
chooses, and a `scatter` formula for a value that the document computes.

## Definitions

A definition names a value or a function. Other rows use the name. Each row of
`definitions` is a LaTeX string, or an object:

| Field    | Type    | Required | Meaning                                                |
| -------- | ------- | -------- | ------------------------------------------------------ |
| `latex`  | string  | yes      | The definition: `"m = 100"`, `"f(x) = e^{-x}\\cos(x)"`. |
| `hidden` | boolean | no       | `true` hides the row's own curve. See below.            |
| `note`   | string  | no       | A text shown above the row.                             |

A function of one variable, such as `f(x) = x^2`, also draws its own curve
$y = f(x)$. Set `"hidden": true` when the function is a helper that must not
draw. A hidden definition still works: other rows can use it.

The parameter of a function belongs to the definition only. It can be a
letter, a letter with a subscript, or a Greek letter written as a LaTeX command:
`f(s) = s^2`, `p(t) = 2t`, `r(x) = x/3` and `q(\sigma) = \sigma + 10` all
work. A parameter can have the same name as a variable. Inside the definition,
the parameter is used, not the variable: with a slider `s` at 5, `f(s) = s^2`
gives `f(3) = 9`, not 25.

## Actions

An action changes variables when it runs. The reader runs it once with its ⚡
button, or a timer runs it again and again. Each row of `actions` is an
object:

| Field      | Type    | Required | Meaning                                                                                  |
| ---------- | ------- | -------- | ---------------------------------------------------------------------------------------- |
| `latex`    | string  | yes      | The action: `"a \\to a + 1"`. See [Tips and Tricks](/tips-and-tricks/en/#change-values-with-actions). |
| `interval` | string  | no       | Run the action again every this many milliseconds (LaTeX: `"100"`).                      |
| `autoplay` | boolean | no       | `true` starts the timer when the document opens. It needs `interval`.                    |
| `note`     | string  | no       | A text shown above the row.                                                              |

An action can change a variable, a coordinate of a point in `points`, or a list
or a point that a definition gives by its value, such as `S = [2.5, 0]`. It
cannot change a name that a formula defines, such as `b = 2a`. Separate several
changes with commas. The action computes all the new values from the old
values first, then writes them together. For a timer that steps a state, see
[A Simulation With a Timer](#a-simulation-with-a-timer).

## Series

A series is one plotted row. Every series has a `type`. Most types take a
formula in `fn`: see the [Plotting Guide](/plotting-guide/en/#how-graph-paper-reads-a-row)
for what a formula can contain, and for the `where` clause and the brace
restrictions that limit what is drawn.

`fn` is LaTeX. Commands such as `\frac{a}{b}`, `\sqrt{x}`, `\sin(x)`,
`\ln(x)` and `\pi` work. Plain text is not LaTeX: Graph Paper reads
`sqrt(x)` as the letters s, q, r and t, not as a square root.

Fields that most series types accept:

| Field    | Type             | Meaning                                                                                              |
| -------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
| `type`   | string           | The series type. Required. See the tables below.                                                     |
| `fn`     | string           | The formula, LaTeX.                                                                                  |
| `domain` | array or object  | The range of the formula's variables. The form depends on the type.                                  |
| `color`  | string or object | A color (`"#c62828"`), a palette name (`"red-700"`) or a colormap name (`"viridis"`).                |
| `name`   | string           | The label in the legend.                                                                             |
| `id`     | string           | A name that another series can refer to, for example in `"fill": { "to": { "series": "lower" } }`. |

How Graph Paper stores each series in the document:

- A series with a string `fn` becomes a formula row. The reader can edit the
  formula. The series `type` is kept, even if the formula alone would give a
  different type.
- A `scatter`, `scatter3d`, `bar`, `histogram`, `candlestick`, `boxplot`,
  `line` or `polar` series with data arrays and no `fn` becomes a table row.
  The reader can edit the numbers.
- A `heatmap`, `surface`, `polygon-list`, `mesh` or `primitives3d` series with
  data and no `fn` becomes a data row. Graph Paper saves the data as given.
  The reader can change the style of the row, but not its numbers.
- A `derived` series becomes a derived row. See
  [Derived Series](#derived-series).

Every series of the document is saved as a row.

## 2D Series Types

| `type`           | Draws                                      | Required               | `domain`                         |
| ---------------- | ------------------------------------------ | ---------------------- | -------------------------------- |
| `line`           | $y = f(x)$                                 | `fn`, or `x` and `y`   | `[min, max]` of $x$              |
| `parametric`     | a curve $(x(t), y(t))$                     | `fn`                   | `[min, max]` of $t$              |
| `polar`          | $r = f(\theta)$                            | `fn`, or `theta` and `r` | `[min, max]` of $\theta$       |
| `implicit`       | a curve $F(x, y) = 0$, or a region from an inequality | `fn`        | `{ "x": [a, b], "y": [c, d] }`   |
| `heatmap`        | a color for each point of $f(x, y)$        | `fn`, or `x`, `y` and `z` | `{ "x": [a, b], "y": [c, d] }` |
| `domainColoring` | a complex function of $z$                  | `fn`                   | `{ "x": [a, b], "y": [c, d] }`   |
| `vector-field`   | arrows $(P(x, y), Q(x, y))$                | `fn`                   | `{ "x": [a, b], "y": [c, d] }`   |
| `scatter`        | points                                     | `fn`, or `x` and `y`   | —                                |
| `polygon-list`   | filled polygons                            | `fn`, or `polygons`    | —                                |
| `bar`            | a bar chart                                | `x` (labels), `y`      | —                                |
| `histogram`      | binned counts of a list of values          | `values`               | —                                |
| `candlestick`    | open, high, low and close for each $x$     | `data`                 | —                                |
| `boxplot`        | box-and-whisker plots                      | `data`                 | —                                |
| `derived`        | a trendline, an average, a smooth curve, a derivative or an integral of another series | `source`, `transform` | — |

Notes on some types:

- `implicit`: an equation draws a curve, an inequality fills a region.
  `"fill"` sets the fill color and `"stroke"` the outline, for example
  `{ "width": 2 }`.
- `heatmap`: `"zRange": [min, max]` fixes the color scale, and
  `"contours": true` adds contour lines.
- `polar`: set `"domain": { "coordinateSystem": "polar" }` at the top level to
  show polar axes.
- `scatter`: `fn` is a point `(a, b)`, a pair of lists `(L_x, L_y)` or a list
  of points. `"line": true` joins the points, and `"points": false` hides the
  markers.
- `polygon-list`: `fn` is a list of `\operatorname{polygon}(…)` values, and
  each polygon takes its vertices as points:
  `[\operatorname{polygon}((0,0), (1,0), (0,1))]`. Each polygon is filled, and
  `"stroke"` adds an outline. Literal `polygons` are accepted too: see
  [Data Without a Formula](#data-without-a-formula).
- `bar`: `"orientation": "horizontal"` turns the bars.
- `histogram`: `"bins"` is a number or `"sturges"`, `"fd"` or `"scott"`.
- `boxplot`: each item of `data` is `{ "min", "q1", "median", "q3", "max" }`;
  `x` gives the labels.

One minimal series of each type, one per line:

```json
{ "type": "line", "fn": "x^2 - 1", "domain": [-3, 3] }
{ "type": "parametric", "fn": "(\\cos(3t), \\sin(2t))", "domain": [0, 6.283185307179586] }
{ "type": "polar", "fn": "1 + \\cos(\\theta)", "domain": [0, 6.283185307179586] }
{ "type": "implicit", "fn": "x^2 + y^2 \\le 1", "domain": { "x": [-2, 2], "y": [-2, 2] } }
{ "type": "heatmap", "fn": "\\sin(x)\\cos(y)", "domain": { "x": [-6, 6], "y": [-6, 6] }, "color": "viridis" }
{ "type": "domainColoring", "fn": "z^2 - 1", "domain": { "x": [-3, 3], "y": [-3, 3] } }
{ "type": "vector-field", "fn": "(-y, x)", "domain": { "x": [-4, 4], "y": [-4, 4] }, "gridSize": 16 }
{ "type": "scatter", "x": [1, 2, 3, 4], "y": [2.1, 3.9, 6.2, 7.8] }
{ "type": "polygon-list", "fn": "[\\operatorname{polygon}((0,0), (1,0), (0,1))]" }
{ "type": "bar", "x": ["A", "B", "C"], "y": [23, 45, 12] }
{ "type": "histogram", "values": [1.2, 0.4, -0.3, 0.8, 1.9, -1.1, 0.2] }
{ "type": "candlestick", "data": [{ "x": 1, "open": 10, "high": 15, "low": 8, "close": 13 }] }
{ "type": "boxplot", "data": [{ "min": 2, "q1": 5, "median": 7, "q3": 9, "max": 14 }], "x": ["A"] }
```

There is no separate contour type: use a `heatmap` with `"contours": true`, or
an `implicit` equation for one level curve.

### A Segment Between Two Points

2D has no segment type. To draw a segment from $P = (p_x, p_y)$ to
$Q = (q_x, q_y)$, use a `parametric` series of $(1 - t)P + tQ$ for
$0 \le t \le 1$, written with the coordinates:

```json
{ "type": "parametric", "fn": "((1 - t)p_x + tq_x, (1 - t)p_y + tq_y)", "domain": [0, 1], "stroke": { "width": 3 } }
```

The ends can be formulas, so the segment moves with them.

A `scatter` series of two points, joined by a line, also draws a segment. Its
`fn` is a list of the two points. The rod of the
[pendulum](#a-simulation-with-a-timer) goes from $(0, 0)$ to the bob, which
follows the angle $a$:

```json
{ "type": "scatter", "fn": "[(0, 0), (L\\sin(a), -L\\cos(a))]", "line": true, "points": false }
```

Do not use `polygon-list` for a segment: its polygons are filled.

## 3D Series Types

| `type`               | Draws                                        | Required                   | `domain`                                     |
| -------------------- | -------------------------------------------- | -------------------------- | -------------------------------------------- |
| `surface`            | $z = f(x, y)$                                | `fn`, or `z`               | `{ "x": [a, b], "y": [c, d] }`               |
| `parametric-surface` | a surface $(x(u, v), y(u, v), z(u, v))$      | `fn`                       | `{ "u": [a, b], "v": [c, d] }`               |
| `parametric-curve`   | a curve $(x(t), y(t), z(t))$                 | `fn`                       | `[min, max]` of $t$                          |
| `implicit-surface`   | a surface $F(x, y, z) = 0$                   | `fn`                       | `{ "x": [a, b], "y": [c, d], "z": [e, f] }`  |
| `scatter3d`          | points in space                              | `fn`, or `x`, `y` and `z`  | —                                            |
| `analyticLandscape`  | the height and phase of a complex function   | `fn`                       | `{ "x": [a, b], "y": [c, d] }`               |
| `primitives3d`       | spheres, segments, arrows and triangles      | `fn`, or `items`           | —                                            |
| `mesh`               | triangles between given points               | `vertices`, `faces`        | —                                            |

Notes on some types:

- `surface`, `parametric-surface`: `"material"` sets the look (for example
  `"glass"`, `"ceramic"`, `"chrome"`), and `"wireframe"` adds grid lines.
  `"wireframe"` is `true`, or an object with these optional fields: `count`
  (the number of divisions on each axis, 11 by default), `width` (the line
  width in pixels, 1 by default), `color` (a hex color) and `style`
  (`"overlay"`, the default, draws the lines on the surface; `"wireframe"`
  draws only the lines; `"lattice"` cuts holes in the surface).
- `parametric-curve`: `"lineWidth"` sets the width of the tube.
- `implicit-surface`: set `"predicate": "=0"`; `"resolution"` (for example
  `64`) sets the detail.
- `scatter3d`: `fn` is a point `(a, b, c)`, three lists `(L_x, L_y, L_z)` or a
  list of points.
- `primitives3d`: `fn` is a list of `\operatorname{sphere}`,
  `\operatorname{segment}`, `\operatorname{vector}` or
  `\operatorname{triangle}` values, often built with `\operatorname{for}`.

One minimal series of each type, one per line:

```json
{ "type": "surface", "fn": "x^2 - y^2", "domain": { "x": [-3, 3], "y": [-3, 3] }, "color": "coolwarm" }
{ "type": "parametric-surface", "fn": "(\\sin(u)\\cos(v), \\sin(u)\\sin(v), \\cos(u))", "domain": { "u": [0, 3.141592653589793], "v": [0, 6.283185307179586] } }
{ "type": "parametric-curve", "fn": "(\\cos(t), \\sin(t), t / (2\\pi))", "domain": [0, 12.566370614359172] }
{ "type": "implicit-surface", "fn": "x^2 + y^2 + z^2 = 4", "predicate": "=0", "domain": { "x": [-3, 3], "y": [-3, 3], "z": [-3, 3] } }
{ "type": "scatter3d", "x": [1, 2, 3], "y": [2, 3, 1], "z": [3, 1, 4] }
{ "type": "analyticLandscape", "fn": "z^3 - 1", "domain": { "x": [-2, 2], "y": [-2, 2] }, "heightMode": "log-magnitude" }
{ "type": "primitives3d", "fn": "[\\operatorname{segment}((0,0,0), (1,1,1))]" }
```

## Data Without a Formula

A series can give its values as literal data instead of a formula in `fn`.
Graph Paper saves each such series as a row of the document.

| `type`         | Data                                   | Row      |
| -------------- | -------------------------------------- | -------- |
| `scatter`      | `x`, `y`                               | table    |
| `scatter3d`    | `x`, `y`, `z`                          | table    |
| `line`         | `x`, `y`                               | table    |
| `polar`        | `theta`, `r`                           | table    |
| `bar`          | `x` (labels), `y`                      | table    |
| `histogram`    | `values`                               | table    |
| `candlestick`  | `data`                                 | table    |
| `boxplot`      | `data`, `x` (labels)                   | table    |
| `heatmap`      | `x`, `y`, `z`                          | data row |
| `surface`      | `z`                                    | data row |
| `polygon-list` | `polygons`, `fillColors` (optional)    | data row |
| `mesh`         | `vertices`, `faces`, `normals` (optional) | data row |
| `primitives3d` | `items`                                | data row |

The reader can edit the numbers of a table. A data row keeps its data as
given: the reader can change its style, hide it, copy it or delete it, but
cannot edit its numbers.

The forms of the data:

- `line`: `x` and `y` are arrays of numbers of the same length. The curve
  goes through the points in the order of the arrays.
- `polar`: `theta` and `r` are arrays of numbers of the same length. The
  angles are in radians, or in degrees with `"angleUnit": "degrees"` on the
  series. The angle unit of the document does not change them.
- `heatmap`: `x` holds the centers of the columns and `y` the centers of the
  rows. `z` has one row for each value of `y`, and each row has one value
  for each value of `x`: the value of the cell at `y[i]`, `x[j]` is `z[i][j]`.
  A `null` value is a hole.
- `surface`: `z` is a grid of at least 2 rows and 2 columns, with the same
  number of values in each row. `"domain": { "x": [a, b], "y": [c, d] }`
  puts it on the axes: the columns spread from $a$ to $b$ and the rows from
  $c$ to $d$. A `null` value is a hole.
- `polygon-list`: `polygons` is a list of `{ "x": [...], "y": [...] }`, each
  with at least 2 vertices. `"strokeClosed": false` on a polygon draws an
  open path, which is stroked and not filled, and `"arrowTo": true` adds an
  arrowhead at its last vertex. `fillColors` gives one fill color for each
  polygon.
- `mesh`: `vertices` is a list of points `[x, y, z]`. `faces` is a list of
  triangles `[i, j, k]`, each an index into `vertices`, from 0. `normals`,
  when given, has one vector `[x, y, z]` for each vertex.
- `primitives3d`: `items` is a list of shapes. Each shape has a `shape`, the
  fields below, and an optional `color`. A point is `[x, y, z]`; a radius, a
  width and a size are numbers greater than 0.

| `shape`                    | Fields                                                        |
| -------------------------- | ------------------------------------------------------------- |
| `sphere`                   | `center`, `radius`                                            |
| `cube`                     | `center`, `size` (a number, or `[width, depth, height]`)      |
| `cylinder`, `cone`, `tube` | `from`, `to`, `radius`                                        |
| `line`                     | `from`, `to`, `width` (optional)                              |
| `arrow`                    | `from`, `to`, and the optional `radius`, `headRadius` and `headLength` |
| `triangle`                 | `vertices`: three points                                      |

One series of each data row type, one per line:

```json
{ "type": "line", "x": [0, 1, 2, 3], "y": [1, 3, 2, 4] }
{ "type": "polar", "theta": [0, 1.5708, 3.1416, 4.7124], "r": [1, 2, 1, 2] }
{ "type": "heatmap", "x": [0, 1, 2], "y": [0, 1], "z": [[1, 2, 3], [4, 5, 6]] }
{ "type": "polygon-list", "polygons": [{ "x": [0, 1, 0], "y": [0, 0, 1] }], "fillColors": ["#1e88e5"] }
{ "type": "surface", "z": [[0, 1, 0], [1, 2, 1], [0, 1, 0]], "domain": { "x": [-1, 1], "y": [-1, 1] } }
{ "type": "mesh", "vertices": [[0, 0, 0], [1, 0, 0], [0, 1, 0]], "faces": [[0, 1, 2]] }
{ "type": "primitives3d", "items": [{ "shape": "sphere", "center": [0, 0, 0], "radius": 1 }] }
```

## Derived Series

A `derived` series draws a computation over another series of the document:
a trendline, a moving average, a smooth curve, a derivative or an integral.
`source` is the `id` of that series, and `transform` says what to compute.
Graph Paper saves it as a derived row, which follows the source when the
reader edits it.

| `transform`                                     | Fields                                                                                                   | Source                 |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------- |
| `{ "kind": "trendline", "model": "linear" }`    | `model`: `"linear"`, `"quadratic"`, `"polynomial"` (with `degree`, 1 to 10), `"exponential"`, `"power"` or `"logarithmic"` | data                   |
| `{ "kind": "sma", "window": 5 }`                | `window`: the number of points, 1 or more                                                                | data, points, derived  |
| `{ "kind": "ema", "alpha": 0.2 }`               | `alpha`: more than 0, at most 1                                                                          | data, points, derived  |
| `{ "kind": "smooth", "tension": 0.5 }`          | `tension` (optional): 0 to 1                                                                             | data, points, derived  |
| `{ "kind": "derivative" }`                      | —                                                                                                        | formula                |
| `{ "kind": "integral" }`                        | —                                                                                                        | formula                |

The source kinds:

- data: a `scatter`, `line` or `candlestick` series with literal data;
- points: a `scatter` series with a formula;
- derived: a `derived` series that comes before this one and is not a
  derivative or an integral;
- formula: a `line` series with a formula.

`source` must name the `id` of exactly one other series, and the transform
must be one that the source takes: Graph Paper does not change a transform
to fit its source, so a mismatch is an error. On a trendline,
`"legendEquation": true` shows the fitted equation in the legend, and
`"legendShowR2": true` adds $R^2$. The style fields are `color`, `stroke`,
`legend` and `legendLabel`; `name` is the text of the legend entry. Without
`color`, the series takes the color of its source.

There is no `derived-marker` type. For the equation of a fit, use a
trendline with `"legendEquation": true`.

## Markers

Markers are annotations on the plot: a label, a segment with an arrow, a
rule across the plot, a band, a rectangle, an ellipse. The top-level
`markers` array holds them. Graph Paper gives each marker its id, and the
reader can move, style and delete it, as a marker placed by hand. Data goes in
series rows; annotations go in markers. A segment between two points of data
is a `line` series, not a marker.

The coordinates of a marker are numbers, not formulas, so a marker does not
move when the reader changes a slider. For a point that follows a variable,
use a `scatter` row with a point formula, such as `"fn": "(0, A)"`. For a
segment that follows a variable, use a `scatter` row whose `fn` is a list of
the two points, with `"line": true` and `"points": false`:

```json
{ "type": "scatter", "fn": "[(0, 0), (L\\sin(a), -L\\cos(a))]", "line": true, "points": false }
```

| `kind`       | Fields                                                    | Dimension |
| ------------ | --------------------------------------------------------- | --------- |
| `point`      | `x`, `y` (and `z` in 3D)                                  | 2D, 3D    |
| `label`      | `x`, `y` (and `z` in 3D), `text`                          | 2D, 3D    |
| `line`       | `x0`, `y0`, `x1`, `y1` (and `z0`, `z1` in 3D): a segment  | 2D, 3D    |
| `rule`       | `axis` (`"x"` or `"y"`), `value`: a line across the plot  | 2D        |
| `band`       | `axis` (`"x"` or `"y"`), `from`, `to`                     | 2D        |
| `rect`       | `x0`, `y0`, `x1`, `y1`                                    | 2D        |
| `ellipse`    | `cx`, `cy`, `rx`, `ry` (radii, more than 0), `angle` (optional, degrees) | 2D |
| `plane-band` | `axis` (`"x"`, `"y"` or `"z"`), `from`, `to`              | 3D        |

A rule with `"axis": "x"` is a vertical line at $x$ = `value`. A band with
`"axis": "x"` covers the $x$ values from `from` to `to`. The bounds of a band
and of a rectangle are sorted. A marker of a 3D document gives its `z`
coordinates; a marker of a 2D document gives none.

`style` (optional) sets the look:

| `kind`                                   | `style` fields                                                                 |
| ---------------------------------------- | ------------------------------------------------------------------------------ |
| `point`                                  | `symbol` (`"circle"`, `"square"`, `"diamond"`, `"cross"`, `"plus"`, `"triangle"`), `size` (pixels), `fill`, `stroke` (`color`, `width`) |
| `label`                                  | `color`, `fontSizeOffset` (−3 to 3), `background`                              |
| `line`                                   | `stroke`, `arrowFrom`, `arrowTo` (booleans: an arrowhead at the start or the end) |
| `rule`                                   | `stroke`                                                                       |
| `band`, `rect`, `ellipse`, `plane-band`  | `fill`, `stroke` (present, even `{}`: an outline)                              |

A `stroke` is `{ "color", "width", "dash" }`, all optional: `width` in pixels,
`dash` a list of lengths such as `[4, 4]`. A color is a hex color
(`"#1e88e5"`, or `"#1e88e533"` with transparency) or `"transparent"`.

The `text` of a label is at most 500 characters. It is plain text, with
inline Markdown (`**bold**`, `*italic*`) and inline math in `$…$`; a text that
is all one `$…$` is a formula.

Rules:

- A document holds at most 100 markers.
- `special-value` markers (a root, an extremum, an intersection that Graph
  Paper finds) cannot be given: mark the place with a `point` or a `label`.
- `stage.markers` and `stage3d.markers` are ignored, with a warning: write
  the markers in the top-level `markers` array.
- A document still needs at least one series.

```json
{
  "title": "Annotated parabola",
  "series": [{ "type": "line", "fn": "x^2", "domain": [-2, 2] }],
  "markers": [
    { "kind": "label", "x": 0, "y": 0.4, "text": "Vertex" },
    { "kind": "line", "x0": 1, "y0": 3, "x1": 1.4, "y1": 2.1, "style": { "arrowTo": true, "stroke": { "width": 2 } } },
    { "kind": "rule", "axis": "x", "value": 1.5 },
    { "kind": "band", "axis": "y", "from": 1, "to": 2, "style": { "fill": "#1e88e533" } }
  ]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkFubm90YXRlZCBwYXJhYm9sYSIsInNlcmllcyI6W3sidHlwZSI6ImxpbmUiLCJmbiI6InheMiIsImRvbWFpbiI6Wy0yLDJdfV0sIm1hcmtlcnMiOlt7ImtpbmQiOiJsYWJlbCIsIngiOjAsInkiOjAuNCwidGV4dCI6IlZlcnRleCJ9LHsia2luZCI6ImxpbmUiLCJ4MCI6MSwieTAiOjMsIngxIjoxLjQsInkxIjoyLjEsInN0eWxlIjp7ImFycm93VG8iOnRydWUsInN0cm9rZSI6eyJ3aWR0aCI6Mn19fSx7ImtpbmQiOiJydWxlIiwiYXhpcyI6IngiLCJ2YWx1ZSI6MS41fSx7ImtpbmQiOiJiYW5kIiwiYXhpcyI6InkiLCJmcm9tIjoxLCJ0byI6Miwic3R5bGUiOnsiZmlsbCI6IiMxZTg4ZTUzMyJ9fV19)

## Axes and Scene

These fields are all optional. When they are absent, Graph Paper fits the view
to the series.

`domain` (2D):

| Field              | Meaning                                                                        |
| ------------------ | ------------------------------------------------------------------------------ |
| `x.range`, `y.range` | The visible range of an axis: `{ "x": { "range": [-5, 5] } }`. An axis has no `min` and `max` fields. |
| `x.scale`, `y.scale` | `"linear"`, `"log"`, `"symlog"` or `"asinh"`.                                 |
| `aspect`           | `"equal"` for the same scale on both axes, `"auto"`, or a number.              |
| `coordinateSystem` | `"cartesian"` or `"polar"`.                                                    |

Graph Paper keeps the x range and derives the y range from it: the visible y
extent is the x extent × `aspect` × (plot height ÷ plot width), and `y.range`
is only the least it shows, so a tall window shows more of the y axis. To show
less of the y axis, set `aspect` to a smaller number, from 0.2 to 5 (`"auto"`
is about 0.618 for a line plot and 1 for an implicit plot or a heat map,
`"equal"` is 1); with a number, when `y.range` does not fit, the x range
widens instead.

`stage` (2D): `gridStyle` is `true`, `false`, `"lines"`, `"dots"` or
`"isometric"`; `frame` is `true` or `false`.

`domain3d` (3D): `x.range`, `y.range`, `z.range` as in 2D, and `aspectMode`,
which sets the shape of the frame. `"auto"` (the default) and `"cube"` scale
each axis so that the frame is a cube. `"data"` draws the three axes at the
same scale, so the frame has the proportions of the axis ranges (or of the
series, when no range is given).

`stage3d` (3D): `environment` is `"abstract"`, `"outdoor"` or `"paper"`;
`projection` is `"perspective"` or `"orthographic"`; `axes` is `true` or
`false`; `camera` is
`{ "position": [x, y, z], "target": [x, y, z], "up": [0, 0, 1] }`.

The plot types accept more fields than this page lists. The fields above are
the ones a document usually needs.

## Patterns

Each pattern below is a short complete document. Start from the one that is
closest to what you want.

### A Point That Moves Along a Curve

A `scatter` row with a point formula of the slider $a$ draws a point on the
curve, and the point moves when the reader drags the slider.

```json
{
  "title": "A point on a parabola",
  "variables": [{ "name": "a", "value": "1", "domain": { "type": "range", "min": "-2", "max": "2" } }],
  "series": [
    { "type": "line", "fn": "x^2", "domain": [-2.5, 2.5] },
    { "type": "scatter", "fn": "(a, a^2)", "marker": { "size": 10 } }
  ]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkEgcG9pbnQgb24gYSBwYXJhYm9sYSIsInZhcmlhYmxlcyI6W3sibmFtZSI6ImEiLCJ2YWx1ZSI6IjEiLCJkb21haW4iOnsidHlwZSI6InJhbmdlIiwibWluIjoiLTIiLCJtYXgiOiIyIn19XSwic2VyaWVzIjpbeyJ0eXBlIjoibGluZSIsImZuIjoieF4yIiwiZG9tYWluIjpbLTIuNSwyLjVdfSx7InR5cGUiOiJzY2F0dGVyIiwiZm4iOiIoYSwgYV4yKSIsIm1hcmtlciI6eyJzaXplIjoxMH19XX0)

### A Segment That Follows a Variable

A `scatter` row whose `fn` is a list of two points, with `"line": true` and
`"points": false`, draws a segment, and its ends can be formulas.

```json
{
  "title": "A segment that turns",
  "variables": [{ "name": "a", "value": "0.8", "domain": { "type": "range", "min": "0", "max": "6.28" } }],
  "series": [
    { "type": "scatter", "fn": "[(0, 0), (\\cos(a), \\sin(a))]", "line": true, "points": false },
    { "type": "scatter", "fn": "(\\cos(a), \\sin(a))" }
  ],
  "domain": { "aspect": "equal", "x": { "range": [-1.5, 1.5] }, "y": { "range": [-1.5, 1.5] } }
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkEgc2VnbWVudCB0aGF0IHR1cm5zIiwidmFyaWFibGVzIjpbeyJuYW1lIjoiYSIsInZhbHVlIjoiMC44IiwiZG9tYWluIjp7InR5cGUiOiJyYW5nZSIsIm1pbiI6IjAiLCJtYXgiOiI2LjI4In19XSwic2VyaWVzIjpbeyJ0eXBlIjoic2NhdHRlciIsImZuIjoiWygwLCAwKSwgKFxcY29zKGEpLCBcXHNpbihhKSldIiwibGluZSI6dHJ1ZSwicG9pbnRzIjpmYWxzZX0seyJ0eXBlIjoic2NhdHRlciIsImZuIjoiKFxcY29zKGEpLCBcXHNpbihhKSkifV0sImRvbWFpbiI6eyJhc3BlY3QiOiJlcXVhbCIsIngiOnsicmFuZ2UiOlstMS41LDEuNV19LCJ5Ijp7InJhbmdlIjpbLTEuNSwxLjVdfX19)

### A Helper Function

A definition with arguments, such as `E_1(s) = …`, is a function that a
series can call. `E_1` and `E_{1}` are the same name. A definition of a
function also draws its curve, so `"hidden": true` hides it here.

```json
{
  "title": "A helper function",
  "variables": [{ "name": "c", "value": "0.5", "domain": { "type": "range", "min": "0", "max": "1" } }],
  "definitions": [{ "latex": "E_1(s) = e^{-cs}\\cos(4s)", "hidden": true }],
  "series": [{ "type": "line", "fn": "E_1(x)", "domain": [0, 10] }]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkEgaGVscGVyIGZ1bmN0aW9uIiwidmFyaWFibGVzIjpbeyJuYW1lIjoiYyIsInZhbHVlIjoiMC41IiwiZG9tYWluIjp7InR5cGUiOiJyYW5nZSIsIm1pbiI6IjAiLCJtYXgiOiIxIn19XSwiZGVmaW5pdGlvbnMiOlt7ImxhdGV4IjoiRV8xKHMpID0gZV57LWNzfVxcY29zKDRzKSIsImhpZGRlbiI6dHJ1ZX1dLCJzZXJpZXMiOlt7InR5cGUiOiJsaW5lIiwiZm4iOiJFXzEoeCkiLCJkb21haW4iOlswLDEwXX1dfQ)

### A Family of Curves From a List

A list in a definition, such as `k = [1, 2, 3]`, makes one curve for each of
its values.

```json
{
  "title": "A family of sine curves",
  "definitions": ["k = [1, 2, 3]"],
  "series": [{ "type": "line", "fn": "\\sin(kx)", "domain": [0, 6.28] }]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkEgZmFtaWx5IG9mIHNpbmUgY3VydmVzIiwiZGVmaW5pdGlvbnMiOlsiayA9IFsxLCAyLCAzXSJdLCJzZXJpZXMiOlt7InR5cGUiOiJsaW5lIiwiZm4iOiJcXHNpbihreCkiLCJkb21haW4iOlswLDYuMjhdfV19)

### A Sum With a Number of Terms From a Slider

`\sum_{k=0}^{n-1}` adds $n$ terms, and $n$ can be a slider. A slider that
counts terms needs `"step": "1"`. Here the sum is the Fourier series of a
square wave, and the dashed row is the square wave.

```json
{
  "title": "Square wave partial sum",
  "variables": [{ "name": "n", "value": "3", "domain": { "type": "range", "min": "1", "max": "25", "step": "1" } }],
  "series": [
    { "type": "line", "fn": "\\frac{4}{\\pi}\\sum_{k=0}^{n-1}\\frac{\\sin((2k+1)x)}{2k+1}" },
    { "type": "line", "fn": "\\operatorname{sign}(\\sin(x))", "stroke": { "dash": [4, 4] } }
  ],
  "domain": { "x": { "range": [-6.283, 6.283] }, "y": { "range": [-2, 2] } }
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IlNxdWFyZSB3YXZlIHBhcnRpYWwgc3VtIiwidmFyaWFibGVzIjpbeyJuYW1lIjoibiIsInZhbHVlIjoiMyIsImRvbWFpbiI6eyJ0eXBlIjoicmFuZ2UiLCJtaW4iOiIxIiwibWF4IjoiMjUiLCJzdGVwIjoiMSJ9fV0sInNlcmllcyI6W3sidHlwZSI6ImxpbmUiLCJmbiI6IlxcZnJhY3s0fXtcXHBpfVxcc3VtX3trPTB9XntuLTF9XFxmcmFje1xcc2luKCgyaysxKXgpfXsyaysxfSJ9LHsidHlwZSI6ImxpbmUiLCJmbiI6Ilxcb3BlcmF0b3JuYW1le3NpZ259KFxcc2luKHgpKSIsInN0cm9rZSI6eyJkYXNoIjpbNCw0XX19XSwiZG9tYWluIjp7IngiOnsicmFuZ2UiOlstNi4yODMsNi4yODNdfSwieSI6eyJyYW5nZSI6Wy0yLDJdfX19)

### A Simulation

An action with an `interval` and `"autoplay": true` changes a variable on a
timer; here the point goes around the circle. The
[pendulum](#a-simulation-with-a-timer) is a longer example.

```json
{
  "title": "A point that goes around",
  "variables": [{ "name": "a", "value": "0", "domain": { "type": "range", "min": "0", "max": "6.28" } }],
  "actions": [{ "latex": "a \\to \\operatorname{mod}(a + 0.05, 6.28)", "interval": "50", "autoplay": true }],
  "series": [
    { "type": "parametric", "fn": "(\\cos(t), \\sin(t))", "domain": [0, 6.28] },
    { "type": "scatter", "fn": "(\\cos(a), \\sin(a))", "marker": { "size": 10 } }
  ],
  "domain": { "aspect": "equal", "x": { "range": [-1.5, 1.5] }, "y": { "range": [-1.5, 1.5] } }
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkEgcG9pbnQgdGhhdCBnb2VzIGFyb3VuZCIsInZhcmlhYmxlcyI6W3sibmFtZSI6ImEiLCJ2YWx1ZSI6IjAiLCJkb21haW4iOnsidHlwZSI6InJhbmdlIiwibWluIjoiMCIsIm1heCI6IjYuMjgifX1dLCJhY3Rpb25zIjpbeyJsYXRleCI6ImEgXFx0byBcXG9wZXJhdG9ybmFtZXttb2R9KGEgKyAwLjA1LCA2LjI4KSIsImludGVydmFsIjoiNTAiLCJhdXRvcGxheSI6dHJ1ZX1dLCJzZXJpZXMiOlt7InR5cGUiOiJwYXJhbWV0cmljIiwiZm4iOiIoXFxjb3ModCksIFxcc2luKHQpKSIsImRvbWFpbiI6WzAsNi4yOF19LHsidHlwZSI6InNjYXR0ZXIiLCJmbiI6IihcXGNvcyhhKSwgXFxzaW4oYSkpIiwibWFya2VyIjp7InNpemUiOjEwfX1dLCJkb21haW4iOnsiYXNwZWN0IjoiZXF1YWwiLCJ4Ijp7InJhbmdlIjpbLTEuNSwxLjVdfSwieSI6eyJyYW5nZSI6Wy0xLjUsMS41XX19fQ)

### Data With Annotations

A `line` row with `x` and `y` data draws the readings, a `label` marker names
a point, and a `band` marker shades a range of $x$.

```json
{
  "title": "Readings with notes",
  "series": [{ "type": "line", "x": [0, 1, 2, 3, 4], "y": [1, 3, 2, 5, 4] }],
  "markers": [
    { "kind": "label", "x": 3, "y": 5.4, "text": "Peak" },
    { "kind": "band", "axis": "x", "from": 1, "to": 2, "style": { "fill": "#1e88e533" } }
  ]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IlJlYWRpbmdzIHdpdGggbm90ZXMiLCJzZXJpZXMiOlt7InR5cGUiOiJsaW5lIiwieCI6WzAsMSwyLDMsNF0sInkiOlsxLDMsMiw1LDRdfV0sIm1hcmtlcnMiOlt7ImtpbmQiOiJsYWJlbCIsIngiOjMsInkiOjUuNCwidGV4dCI6IlBlYWsifSx7ImtpbmQiOiJiYW5kIiwiYXhpcyI6IngiLCJmcm9tIjoxLCJ0byI6Miwic3R5bGUiOnsiZmlsbCI6IiMxZTg4ZTUzMyJ9fV19)

## Complete Examples

### A Line With a Slider

A damped oscillation. The variable $c$ is a slider at rest, with a note. The
definition $f$ is hidden, so it does not draw a second curve. The dashed line is
the envelope.

```json
{
  "title": "Damped oscillation",
  "variables": [
    {
      "name": "c",
      "value": "0.3",
      "domain": { "type": "range", "min": "0.05", "max": "1" },
      "playback": { "mode": "bounce", "direction": "forward", "duration": 5000 },
      "note": "The damping coefficient $c$. Drag the slider to change it."
    }
  ],
  "definitions": [{ "latex": "f(x) = e^{-c x} \\cos(6 x)", "hidden": true }],
  "series": [
    { "type": "line", "fn": "f(x)", "domain": [0, 10] },
    { "type": "line", "fn": "e^{-c x}", "domain": [0, 10], "stroke": { "dash": [4, 4] } }
  ],
  "domain": { "x": { "range": [0, 10] }, "y": { "range": [-1.2, 1.2] } }
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkRhbXBlZCBvc2NpbGxhdGlvbiIsInZhcmlhYmxlcyI6W3sibmFtZSI6ImMiLCJ2YWx1ZSI6IjAuMyIsImRvbWFpbiI6eyJ0eXBlIjoicmFuZ2UiLCJtaW4iOiIwLjA1IiwibWF4IjoiMSJ9LCJwbGF5YmFjayI6eyJtb2RlIjoiYm91bmNlIiwiZGlyZWN0aW9uIjoiZm9yd2FyZCIsImR1cmF0aW9uIjo1MDAwfSwibm90ZSI6IlRoZSBkYW1waW5nIGNvZWZmaWNpZW50ICRjJC4gRHJhZyB0aGUgc2xpZGVyIHRvIGNoYW5nZSBpdC4ifV0sImRlZmluaXRpb25zIjpbeyJsYXRleCI6ImYoeCkgPSBlXnstYyB4fSBcXGNvcyg2IHgpIiwiaGlkZGVuIjp0cnVlfV0sInNlcmllcyI6W3sidHlwZSI6ImxpbmUiLCJmbiI6ImYoeCkiLCJkb21haW4iOlswLDEwXX0seyJ0eXBlIjoibGluZSIsImZuIjoiZV57LWMgeH0iLCJkb21haW4iOlswLDEwXSwic3Ryb2tlIjp7ImRhc2giOls0LDRdfX1dLCJkb21haW4iOnsieCI6eyJyYW5nZSI6WzAsMTBdfSwieSI6eyJyYW5nZSI6Wy0xLjIsMS4yXX19fQ)

### An Implicit Region

A disk that the reader drags, over the region above a parabola. The point
$(c_x, c_y)$ is the center of the disk.

```json
{
  "title": "A disk above a parabola",
  "points": [
    {
      "x": { "name": "c_x", "value": "0.5" },
      "y": { "name": "c_y", "value": "1" },
      "note": "The center of the disk. Drag it on the plot."
    }
  ],
  "series": [
    { "type": "implicit", "fn": "y \\ge x^2 - 2", "domain": { "x": [-3, 3], "y": [-3, 3] }, "fill": "#90caf9" },
    { "type": "implicit", "fn": "(x - c_x)^2 + (y - c_y)^2 \\le 1", "domain": { "x": [-3, 3], "y": [-3, 3] }, "color": "#c62828" }
  ],
  "domain": { "aspect": "equal" }
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IkEgZGlzayBhYm92ZSBhIHBhcmFib2xhIiwicG9pbnRzIjpbeyJ4Ijp7Im5hbWUiOiJjX3giLCJ2YWx1ZSI6IjAuNSJ9LCJ5Ijp7Im5hbWUiOiJjX3kiLCJ2YWx1ZSI6IjEifSwibm90ZSI6IlRoZSBjZW50ZXIgb2YgdGhlIGRpc2suIERyYWcgaXQgb24gdGhlIHBsb3QuIn1dLCJzZXJpZXMiOlt7InR5cGUiOiJpbXBsaWNpdCIsImZuIjoieSBcXGdlIHheMiAtIDIiLCJkb21haW4iOnsieCI6Wy0zLDNdLCJ5IjpbLTMsM119LCJmaWxsIjoiIzkwY2FmOSJ9LHsidHlwZSI6ImltcGxpY2l0IiwiZm4iOiIoeCAtIGNfeCleMiArICh5IC0gY195KV4yIFxcbGUgMSIsImRvbWFpbiI6eyJ4IjpbLTMsM10sInkiOlstMywzXX0sImNvbG9yIjoiI2M2MjgyOCJ9XSwiZG9tYWluIjp7ImFzcGVjdCI6ImVxdWFsIn19)

### Readings With a Trendline

Six readings, as data, and a linear fit with its equation in the legend. The
scatter series has an `id`, and the derived series names it in `source`.

```json
{
  "title": "Readings with a trendline",
  "series": [
    { "type": "scatter", "id": "readings", "x": [1, 2, 3, 4, 5, 6], "y": [2.1, 3.9, 6.2, 7.8, 10.1, 12.2], "name": "Readings" },
    { "type": "derived", "source": "readings", "transform": { "kind": "trendline", "model": "linear" }, "legendEquation": true }
  ]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IlJlYWRpbmdzIHdpdGggYSB0cmVuZGxpbmUiLCJzZXJpZXMiOlt7InR5cGUiOiJzY2F0dGVyIiwiaWQiOiJyZWFkaW5ncyIsIngiOlsxLDIsMyw0LDUsNl0sInkiOlsyLjEsMy45LDYuMiw3LjgsMTAuMSwxMi4yXSwibmFtZSI6IlJlYWRpbmdzIn0seyJ0eXBlIjoiZGVyaXZlZCIsInNvdXJjZSI6InJlYWRpbmdzIiwidHJhbnNmb3JtIjp7ImtpbmQiOiJ0cmVuZGxpbmUiLCJtb2RlbCI6ImxpbmVhciJ9LCJsZWdlbmRFcXVhdGlvbiI6dHJ1ZX1dfQ)

### A Measured Field

A heatmap of measured values: 3 rows ($y = 0, 1, 2$) of 4 values ($x = 0, 1,
2, 3$). The `null` value is a cell with no measure, drawn as a hole.

```json
{
  "title": "Measured field",
  "series": [
    {
      "type": "heatmap",
      "x": [0, 1, 2, 3],
      "y": [0, 1, 2],
      "z": [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, null, 6]],
      "color": "viridis",
      "name": "Temperature"
    }
  ]
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6Ik1lYXN1cmVkIGZpZWxkIiwic2VyaWVzIjpbeyJ0eXBlIjoiaGVhdG1hcCIsIngiOlswLDEsMiwzXSwieSI6WzAsMSwyXSwieiI6W1sxLDIsMyw0XSxbMiwzLDQsNV0sWzMsNCxudWxsLDZdXSwiY29sb3IiOiJ2aXJpZGlzIiwibmFtZSI6IlRlbXBlcmF0dXJlIn1dfQ)

### A 3D Parametric Surface

A torus.

```json
{
  "title": "Torus",
  "series": [
    {
      "type": "parametric-surface",
      "fn": "((2 + \\cos(v))\\cos(u), (2 + \\cos(v))\\sin(u), \\sin(v))",
      "domain": { "u": [0, 6.283185307179586], "v": [0, 6.283185307179586] },
      "color": "viridis"
    }
  ],
  "stage3d": { "environment": "outdoor" },
  "domain3d": { "aspectMode": "data" }
}
```

`"aspectMode": "data"` draws the three axes at the same scale, so the torus
keeps its shape. Without it, the frame is a cube, and the torus, which is 2
units tall and 6 units wide, is stretched into a barrel.

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IlRvcnVzIiwic2VyaWVzIjpbeyJ0eXBlIjoicGFyYW1ldHJpYy1zdXJmYWNlIiwiZm4iOiIoKDIgKyBcXGNvcyh2KSlcXGNvcyh1KSwgKDIgKyBcXGNvcyh2KSlcXHNpbih1KSwgXFxzaW4odikpIiwiZG9tYWluIjp7InUiOlswLDYuMjgzMTg1MzA3MTc5NTg2XSwidiI6WzAsNi4yODMxODUzMDcxNzk1ODZdfSwiY29sb3IiOiJ2aXJpZGlzIn1dLCJzdGFnZTNkIjp7ImVudmlyb25tZW50Ijoib3V0ZG9vciJ9LCJkb21haW4zZCI6eyJhc3BlY3RNb2RlIjoiZGF0YSJ9fQ)

## A Simulation With a Timer

Graph Paper has no row that solves a differential equation. A document can
still simulate a motion: an action steps the state forward on a timer.

- Keep the state in variables, or in a list definition such as `S = [2.5, 0]`.
- Write one action that gives the next state from the current state. The
  action computes all the new values from the old values, then writes them
  together. When a new value needs another new value, write it out in full.
- Give the action an `interval` in milliseconds, and `"autoplay": true` to
  start the timer when the document opens.
- Add a second action without `interval` that puts back the start values. The
  reader runs it with its ⚡ button.
- Draw the state with rows that read it, such as a `scatter` point.

The limits:

- The time step is fixed. You choose it, for example as a definition
  `h = 0.02`. Set `interval` to the step in milliseconds, and the motion plays
  at about real time. When the browser is busy, the motion plays more slowly:
  a missed run is not made up.
- The integration scheme is the formula that you write. A plain Euler step
  ($a \to a + hw$, $w \to w - h\frac{g}{L}\sin(a)$, both from the old
  values) adds energy at each step, and a pendulum then swings higher and
  higher. The semi-implicit Euler step below computes the new $w$ first and
  the new $a$ from it, and the swing keeps its height. For more accuracy, the
  showcase entries [Projectile with Air Drag](/showcase/en/projectile-with-drag)
  and [Predators and Prey](/showcase/en/predator-prey) write a fourth-order
  Runge–Kutta step as a chain of definitions.
- An action can write a value outside the range of its slider. The value is
  kept, and the slider thumb stays at the end of the rail.
- While the timer runs, undo is not available.

### Example: A Pendulum

A pendulum with large swings. The equation of motion is
$a'' = -\frac{g}{L}\sin(a)$, where $a$ is the angle from the vertical. The
state is the angle $a$, the angular velocity $w$ and the time $T$. The timer
runs every 20 ms, and each run is one step of $h = 0.02$ s:

$$
w \to w - h\frac{g}{L}\sin(a), \quad
a \to a + h\left(w - h\frac{g}{L}\sin(a)\right), \quad
T \to T + h
$$

The second action puts the pendulum back at the start angle $A$, at rest. The
red point is the bob, the dark segment is the rod (a `scatter` row of two
points, joined by a line), and the dark point marker is the pivot. The
sliders do not move by themselves: only the timer changes $a$, $w$ and $T$.
The gray point follows
the small-angle formula $a = A\cos(\sqrt{g/L}\,T)$, for comparison. A large swing
takes longer than the formula predicts, so with a start angle of 2.5 radians
the gray point moves ahead of the bob at once.

```json
{
  "title": "Pendulum",
  "variables": [
    { "name": "L", "value": "2", "domain": { "type": "range", "min": "0.5", "max": "3" }, "note": "The length $L$ of the rod, in meters." },
    { "name": "A", "value": "2.5", "domain": { "type": "range", "min": "0.1", "max": "3.1" }, "note": "The start angle $A$, in radians from the vertical. Run **Reset** after you change it." },
    { "name": "a", "value": "2.5", "domain": { "type": "range", "min": "-3.2", "max": "3.2" }, "note": "The state: the angle $a$, the angular velocity $w$ and the time $T$. The timer changes them." },
    { "name": "w", "value": "0", "domain": { "type": "range", "min": "-8", "max": "8" } },
    { "name": "T", "value": "0", "domain": { "type": "range", "min": "0", "max": "60" } }
  ],
  "definitions": [
    { "latex": "g = 9.81", "note": "The gravity $g$, in m/s²." },
    { "latex": "h = 0.02", "note": "The time step $h$, in seconds." }
  ],
  "actions": [
    {
      "latex": "w \\to w - h\\frac{g}{L}\\sin(a), a \\to a + h\\left(w - h\\frac{g}{L}\\sin(a)\\right), T \\to T + h",
      "interval": "20",
      "autoplay": true,
      "note": "One step of the motion every 20 ms: first the new $w$, then the new $a$ from the new $w$."
    },
    { "latex": "a \\to A, w \\to 0, T \\to 0", "note": "Reset: back to the start angle, at rest." }
  ],
  "series": [
    { "type": "parametric", "fn": "(L\\cos(t), L\\sin(t))", "domain": [0, 6.283185307179586], "color": "#b0bec5", "stroke": { "dash": [4, 4] } },
    { "type": "scatter", "fn": "[(0, 0), (L\\sin(a), -L\\cos(a))]", "line": true, "points": false, "color": "#455a64" },
    { "type": "scatter", "fn": "(L\\sin(A\\cos(\\sqrt{g/L}T)), -L\\cos(A\\cos(\\sqrt{g/L}T)))", "color": "#90a4ae", "marker": { "size": 10 } },
    { "type": "scatter", "fn": "(L\\sin(a), -L\\cos(a))", "color": "#c62828", "marker": { "size": 14 } }
  ],
  "markers": [
    { "kind": "point", "x": 0, "y": 0, "style": { "fill": "#455a64", "size": 8 } }
  ],
  "domain": { "aspect": "equal", "x": { "range": [-3.5, 3.5] }, "y": { "range": [-3.5, 3.5] } }
}
```

[Open this document in Graph Paper](https://graph-paper.io/new#doc=eyJ0aXRsZSI6IlBlbmR1bHVtIiwidmFyaWFibGVzIjpbeyJuYW1lIjoiTCIsInZhbHVlIjoiMiIsImRvbWFpbiI6eyJ0eXBlIjoicmFuZ2UiLCJtaW4iOiIwLjUiLCJtYXgiOiIzIn0sIm5vdGUiOiJUaGUgbGVuZ3RoICRMJCBvZiB0aGUgcm9kLCBpbiBtZXRlcnMuIn0seyJuYW1lIjoiQSIsInZhbHVlIjoiMi41IiwiZG9tYWluIjp7InR5cGUiOiJyYW5nZSIsIm1pbiI6IjAuMSIsIm1heCI6IjMuMSJ9LCJub3RlIjoiVGhlIHN0YXJ0IGFuZ2xlICRBJCwgaW4gcmFkaWFucyBmcm9tIHRoZSB2ZXJ0aWNhbC4gUnVuICoqUmVzZXQqKiBhZnRlciB5b3UgY2hhbmdlIGl0LiJ9LHsibmFtZSI6ImEiLCJ2YWx1ZSI6IjIuNSIsImRvbWFpbiI6eyJ0eXBlIjoicmFuZ2UiLCJtaW4iOiItMy4yIiwibWF4IjoiMy4yIn0sIm5vdGUiOiJUaGUgc3RhdGU6IHRoZSBhbmdsZSAkYSQsIHRoZSBhbmd1bGFyIHZlbG9jaXR5ICR3JCBhbmQgdGhlIHRpbWUgJFQkLiBUaGUgdGltZXIgY2hhbmdlcyB0aGVtLiJ9LHsibmFtZSI6InciLCJ2YWx1ZSI6IjAiLCJkb21haW4iOnsidHlwZSI6InJhbmdlIiwibWluIjoiLTgiLCJtYXgiOiI4In19LHsibmFtZSI6IlQiLCJ2YWx1ZSI6IjAiLCJkb21haW4iOnsidHlwZSI6InJhbmdlIiwibWluIjoiMCIsIm1heCI6IjYwIn19XSwiZGVmaW5pdGlvbnMiOlt7ImxhdGV4IjoiZyA9IDkuODEiLCJub3RlIjoiVGhlIGdyYXZpdHkgJGckLCBpbiBtL3PCsi4ifSx7ImxhdGV4IjoiaCA9IDAuMDIiLCJub3RlIjoiVGhlIHRpbWUgc3RlcCAkaCQsIGluIHNlY29uZHMuIn1dLCJhY3Rpb25zIjpbeyJsYXRleCI6IncgXFx0byB3IC0gaFxcZnJhY3tnfXtMfVxcc2luKGEpLCBhIFxcdG8gYSArIGhcXGxlZnQodyAtIGhcXGZyYWN7Z317TH1cXHNpbihhKVxccmlnaHQpLCBUIFxcdG8gVCArIGgiLCJpbnRlcnZhbCI6IjIwIiwiYXV0b3BsYXkiOnRydWUsIm5vdGUiOiJPbmUgc3RlcCBvZiB0aGUgbW90aW9uIGV2ZXJ5IDIwIG1zOiBmaXJzdCB0aGUgbmV3ICR3JCwgdGhlbiB0aGUgbmV3ICRhJCBmcm9tIHRoZSBuZXcgJHckLiJ9LHsibGF0ZXgiOiJhIFxcdG8gQSwgdyBcXHRvIDAsIFQgXFx0byAwIiwibm90ZSI6IlJlc2V0OiBiYWNrIHRvIHRoZSBzdGFydCBhbmdsZSwgYXQgcmVzdC4ifV0sInNlcmllcyI6W3sidHlwZSI6InBhcmFtZXRyaWMiLCJmbiI6IihMXFxjb3ModCksIExcXHNpbih0KSkiLCJkb21haW4iOlswLDYuMjgzMTg1MzA3MTc5NTg2XSwiY29sb3IiOiIjYjBiZWM1Iiwic3Ryb2tlIjp7ImRhc2giOls0LDRdfX0seyJ0eXBlIjoic2NhdHRlciIsImZuIjoiWygwLCAwKSwgKExcXHNpbihhKSwgLUxcXGNvcyhhKSldIiwibGluZSI6dHJ1ZSwicG9pbnRzIjpmYWxzZSwiY29sb3IiOiIjNDU1YTY0In0seyJ0eXBlIjoic2NhdHRlciIsImZuIjoiKExcXHNpbihBXFxjb3MoXFxzcXJ0e2cvTH1UKSksIC1MXFxjb3MoQVxcY29zKFxcc3FydHtnL0x9VCkpKSIsImNvbG9yIjoiIzkwYTRhZSIsIm1hcmtlciI6eyJzaXplIjoxMH19LHsidHlwZSI6InNjYXR0ZXIiLCJmbiI6IihMXFxzaW4oYSksIC1MXFxjb3MoYSkpIiwiY29sb3IiOiIjYzYyODI4IiwibWFya2VyIjp7InNpemUiOjE0fX1dLCJtYXJrZXJzIjpbeyJraW5kIjoicG9pbnQiLCJ4IjowLCJ5IjowLCJzdHlsZSI6eyJmaWxsIjoiIzQ1NWE2NCIsInNpemUiOjh9fV0sImRvbWFpbiI6eyJhc3BlY3QiOiJlcXVhbCIsIngiOnsicmFuZ2UiOlstMy41LDMuNV19LCJ5Ijp7InJhbmdlIjpbLTMuNSwzLjVdfX19)

## Check a Document

A program can check a document without a browser and without an account:

- Send it with `POST https://graph-paper.io/plot-link`, as the JSON body. A
  program that can only fetch a URL can use
  `GET https://graph-paper.io/plot-link?doc=<percent-encoded JSON>` (write a
  space as `%20` and a plus sign as `%2B`). A `GET` always answers with
  status 200; the `status` field of the answer is the status that a `POST`
  gets.
- Or call the tool `make_plot_link` of the MCP server
  `https://graph-paper.io/mcp`.

The answer is the link that opens the document, or each error with the path of
its field, such as `series[0].fn`. Warnings name the keys that were ignored.
[For Agents](/for-agents/en/) shows the requests and the answers.

The check covers the fields, their types and the limits on this page. It does
not draw the plot. A formula that is valid LaTeX can still draw nothing. Tell
the person that you did not see the plot, and prefer the row forms that this
page shows.

The link service checks the structure only. Graph Paper checks the formulas
when the link opens, and reports the rows that do not work: see
[When the Person Opens the Link](/for-agents/en/#when-the-person-opens-the-link)
on the For Agents page.

## Learn From the Showcase

Every [showcase](/showcase/en/) page contains its document in the same shape,
in a `<script type="application/graph-paper+json" id="gallery-entry">` tag. When
the document is large, the tag holds only `{ "id": … }` and a
`data-payload-href` attribute that gives the URL of the full JSON.

A showcase entry is not a document to send as it is. It has an `id` and no
`title`, and its `note` values are the names of translated texts, not the texts.
To reuse one, remove `id`, add a `title`, and replace or remove each `note`.

## Notebook Documents

Graph Paper also has notebook documents: sections of text, formulas,
computations and figures. They are a different format, and a `/new` link cannot
open them. Their reference is the
[notebook file format](/docs/notebook-file-format.md), with JSON schemas for
[the export file](/docs/schema/export-envelope.schema.json) and
[the notebook content](/docs/schema/notebook-content.schema.json).
