Graph Paper

Plotting Guide

A program that wants to give a person a plot starts at For Agents: the fastest way is to send a plot 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.
Tips and Tricks covers the everyday shortcuts. This page goes deeper: how Graph Paper decides what a row means, the list and point notation that complex plots are built from, and — if you are arriving from Desmos — which habits carry over and which ones need a small edit.

Contents

How Graph Paper Reads a Row

Every row in a document is read as exactly one of three things. Knowing which one you wrote explains most surprises.
The row isWhen it looks likeWhat you see
A definitionf(x) = \sin(x), a = 3Nothing is drawn; the name is available to every other row
DataL = [1, 2, 3]The values are listed; nothing is drawn
A plotx^2, y = x^2, (\cos t, \sin t)A series on the graph
Two more kinds of row change values rather than draw them: an action (a \to a + 1) changes variables when it runs, and a fit (Y \sim aX + b) finds parameter values from data. Both are covered in Tips and Tricks.
A definition is not drawn even though it contains a formula — that is the point of naming it. To see it, add a second row that uses the name:
f(x) \coloneq \sin(x)2f(x) + 1
Both = and \coloneq define a function. Use \coloneq when you want to be explicit that the row is a definition rather than an equation to solve; the two behave the same. Names can be a single letter, a letter with a subscript (f_1), or a whole word.
A list on its own is data, not a plot. L = [1, 2, 3] shows you the values and draws nothing — pair it with something that says where to draw, such as (L, L^2).

Work with Lists and Ranges

Lists are the backbone of complex plots. Write one directly, or generate it:
You wantTypeYou get
A literal list[1, 2, 3][1, 2, 3]
A range[1...10]the integers 1 to 10
A range with a step[1, 3...11][1, 3, 5, 7, 9, 11]
A transformed list[1...5]^2[1, 4, 9, 16, 25]
A comprehension[k^2 \text{ for } k=[1...5]][1, 4, 9, 16, 25]
Sorted\operatorname{sort}(L)the values in order
Duplicates removed\operatorname{unique}(L)each value once
Arithmetic applies to every element — [1...5]^2 and the comprehension above are two spellings of the same thing. The power form is shorter; the comprehension is clearer once the expression grows or uses the index more than once.
Lists combine with parameters to draw a whole family at once. Give k a set of values and plot \sin(kx) — you get one curve per value:
k = [1, 3, 5, 7, 9]\sin(k x)

Index, Slice, and Filter

Indexing starts at 1, and every form below returns a list when you give it a list of positions.
You wantTypeNotes
One elementL[2]The second element.
Count from the endL[-1]The last element.
A sliceL[[2...4]]Elements 2 through 4, as a list.
Only some elementsL[L>2]Keeps the elements where the test is true.
The same, spelled out\operatorname{Filter}(L, x \mapsto x>2)Useful when the test is not about the element itself.
Transform each\operatorname{Map}(x \mapsto x^2, L)The long form of L^2.
Asking for an element that is not there gives you an undefined value rather than an error, and points built from it are skipped when the curve is drawn. That is deliberate — one bad index does not blank the whole plot.

Build Points and Polygons

A parenthesized pair is a point, and a point whose coordinates are lists is a list of points. This is how scatter plots and polygons get built:
You wantType
One point(1, 2)
A list of points(L, L^2)
The distance between two points\operatorname{Distance}((0,0), (3,4))
The midpoint\operatorname{midpoint}((0,0), (3,4))
Points also support ordinary arithmetic, so the midpoint can be written out as \frac{(0,0) + (3,4)}{2} if you prefer: adding two points adds their coordinates, and dividing scales them.

Read Coordinates Back

Attach .x, .y, or .z to any point to pull out one coordinate:
You haveTypeYou get
A = (3, 4)A.x3
A = (3, 4)A.y4
V = (1, 2, 3)V.z3
A point straight out(3,4).x3
The accessor works on whatever produced the point, so it chains with indexing — S[2].x is the x of the second point in a list S.
On a list of points it gives you the whole column. If S = [(0,1), (2,3), (4,5)], then S.x is [0, 2, 4] and S.y is [1, 3, 5] — no loop or comprehension needed. That makes it the natural way to take one axis of a dataset apart and put it back together differently:
(S.y, S.x)
plots the same points with the axes swapped. Coordinates read this way are ordinary numbers, so they combine freely with everything else — A.x + A.y is 7.
.x, .y, and .z are the only accessors. Everything you do to a list is written as a function call rather than a property — \operatorname{count}(L), not L.\mathrm{length}; \operatorname{total}(L), not L.\mathrm{total}.
For a closed shape, collect the vertices into a list and pass it to \operatorname{polygon}:
P = [(0,0), (1,0), (0,1)]\operatorname{polygon}(P)
If a vertex in the list is undefined, the polygon breaks there rather than bridging the gap — so a single undefined vertex splits one polygon into two pieces instead of drawing a wrong edge across the middle.

Draw From a Table of Data

Once a drawing has more than a few pieces, one row per piece stops scaling — and the repetition buries the structure of the thing you are drawing. Name the data instead, and let a single row draw all of it.
Two rows of data: where the points are, and which points make up each piece.
V = [(0,0), (2,0), (2,1), (0,1)]F = [[1,2,3], [1,3,4]]
V is a list of points. F says the first piece is built from vertices 1, 2 and 3, the second from 1, 3 and 4. Neither row draws anything — they are data. One more row draws every piece:
[\operatorname{polygon}(V[f]) \text{ for } f = F]
That works because indexing with a list of positions returns a list: for f = [1,2,3], V[f] is those three points, which is exactly what \operatorname{polygon} takes. The comprehension runs it once per entry of F.
In 3D the same split applies, naming each corner separately:
[\operatorname{triangle}(V[f[1]], V[f[2]], V[f[3]]) \text{ for } f = F]
A list of vertices plus a table of faces is how shapes are described almost everywhere, so one written this way can be read as data rather than decoded from a wall of coordinates. Move a corner and every piece using it follows; change which vertices a face joins and only F changes.

Restrict What Gets Drawn

There are two ways to limit a plot, and they answer different questions.
Braces restrict where the formula is true. Put a condition in braces after any row and it applies to the whole row:
y = x^2 \left\{ x > 0 \right\}
Chain them for several conditions, and use a two-sided form when it reads better:
y = x^2 \left\{ 0 < x < 3 \right\}
Braces work on every kind of row — explicit functions, implicit curves, regions, and parametric curves:
x^2 + y^2 = 1 \left\{ y > 0 \right\}
A where clause sets a parameter's range. For parametric and polar curves, this is the one that controls how much of the path is traced:
(\cos(t), \sin(t)) \text{ where } 0 \le t \le \pi
The same clause introduces local helpers, which keeps a long formula readable without creating sliders:
(r\cos(u), r\sin(u), u) \text{ where } r \coloneq 2 + \sin(u)
Reach for braces when you are masking a region of the plane, and for where when you are bounding the parameter that draws the curve.

Write Piecewise Formulas

Two spellings work, and they mean the same thing. The cases environment is the conventional mathematical notation:
\begin{cases} x^2 & x < 0 \\ x & x \ge 0 \end{cases}
The compact brace form puts the condition and value on one line, separated by a colon, with a final unconditional fallback:
\left\{ x < 0 : x^2, x \right\}
Use whichever you find easier to read. When branches repeat an expression, factor it into a named helper above so the branches stay short.

Set Colors

Color channels are on a 0 to 1 scale:
You wantType
Red\operatorname{rgb}(1, 0, 0)
Orange\operatorname{rgb}(1, 0.5, 0)
Mid grey\operatorname{rgb}(0.5, 0.5, 0.5)
By hue\operatorname{hsv}(120, 1, 1)
\operatorname{hsv} takes a hue in degrees with saturation and value from 0 to 1, so hue-based colors need no conversion.
If you have channel values on the usual 0-to-255 scale, use \operatorname{rgb}_{255} instead of converting by hand:
C \coloneq \operatorname{rgb}_{255}(100, 150, 200)
Assign a color to a name that way and reuse it across rows. Note that \operatorname{rgb} itself clips values above 1, so passing 0-to-255 numbers to the wrong one of the two fails quietly — see Coming from Desmos.

Summarize Data

These read a list and return a single number:
You wantType
The total\operatorname{total}(L) or \sum L
The mean\operatorname{mean}(L)
The median\operatorname{median}(L)
The smallest, largest\operatorname{min}(L), \operatorname{max}(L)
The variance\operatorname{var}(L)
The standard deviation\operatorname{stdev}(L)
How many elements\operatorname{count}(L)
For distributions, \operatorname{histogram}(L) bins the values with a default bin width of 1, and \operatorname{histogram}(L, 2) sets the width explicitly. Box plots are built from a table's data rather than from a formula — choose them from the table's plot type control.
A row that reduces a list to one number plots as a horizontal line at that value, which is a quick way to draw a mean or a threshold across a scatter plot.

Coming from Desmos

Most of what you know transfers unchanged. These all work exactly as they do in Desmos — restriction braces, [1...10] ranges, list comprehensions, L[L>2] filtering, .x accessors, point arithmetic, \operatorname{polygon}, \operatorname{histogram}, defining functions with =, single-letter names with subscripts, and the list and statistics names \operatorname{total}, \operatorname{sort}, \operatorname{unique}, \operatorname{stdev}, \operatorname{mean}, \operatorname{median}, \operatorname{nPr} and \operatorname{nCr}. So do the interactive features: actions written with \to, the ticker, click actions with \mathrm{index}, draggable points, regressions written with \sim, and ${a} templates in labels — see Tips and Tricks for how each one is set up here.
A few things differ:
In DesmosIn Graph PaperWhy
\operatorname{rgb}(255, 0, 0)\operatorname{rgb}_{255}(255, 0, 0)\operatorname{rgb} takes 0 to 1.
a \operatorname{with} b=2a \text{ where } b=2where is the general form.
L.\mathrm{total}\operatorname{total}(L)List operations are function calls.
\operatorname{boxplot}(L)a table's plot typeBox plots come from table data.
the ticker panel▶ on an action cellThe interval is a row of its own.
clickableInfo on a rowOn click… in the cell's ⋯ menuSame \to syntax, same index.
Desmos's list properties — L.\mathrm{total}, L.\mathrm{length}, L.\mathrm{min}, L.\mathrm{sort} and the rest — are all written as function calls here. Only points take a dot, and only for .x, .y, and .z.
The color one is worth a second look, because it fails quietly. Channel values above 1 are clipped, so a fully saturated color still looks correct and everything else drifts toward white:
You typeYou get
\operatorname{rgb}(255, 0, 0)redcorrect — by coincidence
\operatorname{rgb}(255, 128, 0)yellowyou wanted orange
\operatorname{rgb}(100, 150, 200)whiteyou wanted steel blue
\operatorname{rgb}(34, 34, 34)whiteyou wanted near-black
Reach for \operatorname{rgb}_{255} whenever your numbers are on the 0-to-255 scale, and the conversion is done for you.
One more difference worth knowing: \operatorname{gcd} and \operatorname{lcm} in Graph Paper work on the numbers you give them, while Desmos first rounds its arguments to whole numbers. If you are relying on that rounding, write it out — \operatorname{gcd}(\operatorname{round}(a), \operatorname{round}(b)).
Importing takes care of all of this. Open a Desmos graph through import and these conversions are applied for you; the table above is for formulas you type yourself.

When a Row Draws Nothing

Work down this list before rewriting the formula.
SymptomLikely cause
The row shows values instead of a plotIt is a list or a definition, not a plot. Add a row that uses it.
A function name behaves like multiplicationGraph Paper does not know that name, so it reads it as a product. Check the spelling against the tables above.
A color came out whiteChannels above 1 are clipped. Use \operatorname{rgb}_{255}.
Part of a curve is missingA brace condition or a where range is narrower than you meant.
A polygon has a gapOne of its vertices is undefined, which splits it in two.
Nothing at all appearsThe viewport may be somewhere else — check the axis ranges before the formula.
A handful of names are reserved for built-in functions, so a row like \operatorname{mean}(x) = x^2 does not define your own function — rename it if you need one of those.
For everything else, see Tips and Tricks or Support.