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: 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.
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, the Plotting Guide and Tips and
Tricks.
Contents
- A Minimal Document
- Rules and Limits
- Top-Level Fields
- Variables
- Points
- A Point That Moves
- Definitions
- Actions
- Series
- 2D Series Types
- 3D Series Types
- Data Without a Formula
- Derived Series
- Markers
- A Segment Between Two Points
- Axes and Scene
- Patterns
- Complete Examples
- A Simulation With a Timer
- Check a Document
- Learn From the Showcase
- 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 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.
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 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.
- 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. |
| variables | array | no | Sliders. See Variables. |
| points | array | no | Points the reader drags on the plot. See Points. |
| definitions | array | no | Named values and functions that other rows use. See Definitions. |
| actions | array | no | Rows that change variables, once or on a timer. See Actions. |
| markers | array | no | Annotations on the plot: labels, segments, rules, bands. See Markers. |
| domain | object | no | 2D axis ranges, aspect ratio and coordinate system. See 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 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. |
| 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.
Series
A series is one plotted row. Every series has a type. Most types take a
formula in fn: see the Plotting Guide
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.
Every series of the document is saved as a row.
2D Series Types
| type | Draws | Required | domain |
|---|---|---|---|
| line | fn, or x and y | [min, max] of | |
| parametric | a curve | fn | [min, max] of |
| polar | fn, or theta and r | [min, max] of | |
| implicit | a curve | fn | { "x": [a, b], "y": [c, d] } |
| heatmap | a color for each point of | fn, or x, y and z | { "x": [a, b], "y": [c, d] } |
| domainColoring | a complex function of | fn | { "x": [a, b], "y": [c, d] } |
| vector-field | arrows | 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 | 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.
- 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 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 | fn, or z | { "x": [a, b], "y": [c, d] } | |
| parametric-surface | a surface | fn | { "u": [a, b], "v": [c, d] } |
| parametric-curve | a curve | fn | [min, max] of |
| implicit-surface | a surface | 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 tob and the rows fromc tod . 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" } }
]
}
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 } }
]
}
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] } }
}
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] }]
}
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] }]
}
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] } }
}
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 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] } }
}
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" } }
]
}
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] } }
}
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" }
}
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 }
]
}
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"
}
]
}
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.
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 neww first and the newa from it, and the swing keeps its height. For more accuracy, the showcase entries Projectile with Air Drag and Predators and 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:
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] } }
}
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 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
on the For Agents page.
Learn From the Showcase
Every showcase 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, with JSON schemas for
the export file and
the notebook content.