# Vector mark

The **vector mark** draws little arrows, typically positioned in **x** and **y** quantitative dimensions, with an optional magnitude (**length**) and direction (**rotate**), as in a vector field. For example, the chart below, based on a LitVis example, shows wind speed and direction for a section of western Europe.

```
Plot.plot({
inset: 10,
aspectRatio: 1,
color: {label: "Speed (m/s)", zero: true, legend: true},
marks: [
Plot.vector(wind, {
x: "longitude",
y: "latitude",
rotate: ({u, v}) => Math.atan2(u, v) * 180 / Math.PI,
length: ({u, v}) => Math.hypot(u, v),
stroke: ({u, v}) => Math.hypot(u, v)
})
]
})
```

INFO

Regarding this data, Remote Sensing Systems says: *“Standard U and V coordinates apply, meaning the positive U is to the right and positive V is above the axis. U and V are relative to true north. CCMP winds are expressed using the oceanographic convention, meaning a wind blowing toward the Northeast has a positive U component and a positive V component… Longitude is given in degrees East from 0.125 to 359.875 and latitude is given in degrees North with negative values representing southern locations.”*

Vectors can be used with Plot’s projection system. The map below shows the margin by which the winner of the US presidential election of 2020 won the vote in each county. The arrow’s length encodes the difference in votes, and the orientation and color show who won ( for the Democratic candidate, and for the Republican candidate).

Fork```
Plot.plot({
projection: "albers-usa",
length: {type: "sqrt", transform: Math.abs},
marks: [
Plot.geo(statemesh, {strokeWidth: 0.5}),
Plot.geo(nation),
Plot.vector(
counties,
Plot.centroid({
anchor: "start",
length: (d) => d.properties.margin2020 * d.properties.votes,
stroke: (d) => d.properties.margin2020 > 0 ? "red" : "blue",
rotate: (d) => d.properties.margin2020 > 0 ? 60 : -60
})
)
]
})
```

The **shape** option controls the vector’s appearance, while the **anchor** option positions the vector relative to its anchor point specified in **x** and **y**. The spike constructor sets the **shape** to *spike* and the **anchor** to *start*. For example, this can be used to produce a spike map of U.S. county population.

```
Plot.plot({
width: 688,
projection: "albers-usa",
length: {range: [0, 200]},
marks: [
Plot.geo(statemesh, {strokeOpacity: 0.5}),
Plot.geo(nation),
Plot.spike(counties, Plot.geoCentroid({length: (d) => d.properties.population, stroke: "green"}))
]
})
```

You can even implement a custom **shape** by supplying an object with a **draw** method. This method takes a *context* for drawing paths and the *length* of the vector. See the moon phase calendar for an example.

Lastly, here is an example showing a Perlin noise field, just because it’s pretty:

Fork```
Plot.plot({
inset: 6,
width: 1024,
aspectRatio: 1,
axis: null,
marks: [
Plot.vector(poisson([0, 0, 2, 2], 4000), {
length: ([x, y]) => (noise(x + 2, y) + 0.5) * 24,
rotate: ([x, y]) => noise(x, y) * 360
})
]
})
```

This example uses a noise(*x*, *y*) function defined as:

`noise = octave(perlin2, 2)`

See Mike Bostock’s Perlin Noise and Poisson Disk Sampling notebooks for source code.

## Vector options

In addition to the standard mark options, the following optional channels are supported:

**x**- the horizontal position; bound to the*x*scale**y**- the vertical position; bound to the*y*scale**length**- the length in pixels; bound to the*length*scale; defaults to 12**rotate**- the rotation angle in degrees clockwise; defaults to 0

If either of the **x** or **y** channels are not specified, the corresponding position is controlled by the **frameAnchor** option.

The following constant options are also supported:

**shape**- the shape of the vector; defaults to*arrow***r**- a radius in pixels; defaults to 3.5**anchor**- one of*start*,*middle*, or*end*; defaults to*middle***frameAnchor**- how to position the vector within the frame; defaults to*middle*

The **shape** option controls the visual appearance (path geometry) of the vector and supports the following values:

*arrow*(default) - an arrow with head size proportional to its length*spike*- an isosceles triangle with open base- any object with a
**draw**method; it receives a*context*,*length*, and*radius*

If the **anchor** is *start*, the arrow will start at the given *xy* position and point in the direction given by the rotation angle. If the **anchor** is *end*, the arrow will maintain the same orientation, but be positioned such that it ends in the given *xy* position. If the **anchor** is *middle*, the arrow will be likewise be positioned such that its midpoint intersects the given *xy* position.

If the **x** channel is not specified, vectors will be horizontally centered in the plot (or facet). Likewise if the **y** channel is not specified, vectors will be vertically centered in the plot (or facet). Typically either *x*, *y*, or both are specified.

The **rotate** and **length** options can be specified as either channels or constants. When specified as a number, it is interpreted as a constant; otherwise it is interpreted as a channel. The length defaults to 12 pixels, and the rotate defaults to 0 degrees (pointing up↑). Vectors with a negative length will be drawn inverted. Positive angles proceed clockwise from noon.

The **stroke** defaults to *currentColor*. The **strokeWidth** defaults to 1.5, and the **strokeLinecap** defaults to *round*.

Vectors are drawn in input order, with the last data drawn on top. If sorting is needed, say to mitigate overplotting by drawing the smallest vectors on top, consider a sort transform.

## vector(*data*, *options*)

`Plot.vector(wind, {x: "longitude", y: "latitude", length: "speed", rotate: "direction"})`

Returns a new vector with the given *data* and *options*. If neither the **x** nor **y** options are specified, *data* is assumed to be an array of pairs [[*x₀*, *y₀*], [*x₁*, *y₁*], [*x₂*, *y₂*], …] such that **x** = [*x₀*, *x₁*, *x₂*, …] and **y** = [*y₀*, *y₁*, *y₂*, …].

## vectorX(*data*, *options*)

`Plot.vectorX(cars.map((d) => d["economy (mpg)"]))`

Equivalent to vector except that if the **x** option is not specified, it defaults to the identity function and assumes that *data* = [*x₀*, *x₁*, *x₂*, …].

## vectorY(*data*, *options*)

`Plot.vectorY(cars.map((d) => d["economy (mpg)"]))`

Equivalent to vector except that if the **y** option is not specified, it defaults to the identity function and assumes that *data* = [*y₀*, *y₁*, *y₂*, …].

## spike(*data*, *options*)

`Plot.spike(counties, Plot.geoCentroid({length: (d) => d.properties.population}))`

Equivalent to vector except that the **shape** defaults to *spike*, the **stroke** defaults to *currentColor*, the **strokeWidth** defaults to 1, the **fill** defaults to **stroke**, the **fillOpacity** defaults to 0.3, and the **anchor** defaults to *start*.