# Movable points

Movable points can be dragged around the coordinate plane, or moved via the keyboard. They're the cornerstone of lots of interactions.

They can be unconstrained (allowed to move freely), constrained horizontally or vertically, or constrained to an arbitrary function. This example constrains movement horizontally:

``````import { Mafs, Plot, Point, Coordinates, useMovablePoint } from "mafs"
import range from "lodash/range"

function PointsAlongFunction() {
const fn = (x: number) => (x / 2) ** 2
const sep = useMovablePoint([1, 0], {
constrain: "horizontal",
})

const n = 10

const points =
sep.x != 0
? range(-n * sep.x, (n + 0.5) * sep.x, sep.x)
: []

return (
<Mafs
viewBox={{ x: [0, 0], y: [-1.3, 4.7] }}
>
<Coordinates.Cartesian />

<Plot.OfX y={fn} opacity={0.25} />
{points.map((x, index) => (
<Point x={x} y={fn(x)} key={index} />
))}
{sep.element}
</Mafs>
)
}``````

## Constraints

Beyond constraining horizontally or vertically, points can also be constrained to arbitrary paths. This is done by passing a function to `constrain`. The function is expected to take a point (x, y), which is where the user is trying to move to, and to return a new point, (x', y'), which is where the point should actually go.

To demonstrate this, imagine constraining a point to "snap" to the nearest whole number point. We take where the user is trying to move to, and round it to the nearest whole number.

``````useMovablePoint([0, 0], {
constrain: ([x, y]) => [Math.round(x), Math.round(y)]
})``````

Another common use case is to constrain motion to be circular—`vec.withMag` comes in handy there.

``````useMovablePoint([0, 0], {
// Constrain `point` to move in a circle of radius 1
constrain: (point) => vec.withMag(point, 1)
})``````

You can also constrain a point to follow a straight line by setting `constrain` to `"horizontal"` or `"vertical"`.

Mafs may call `constrain` more than once when the user moves a point using the arrow keys, so it should be side-effect free.

## Transformations and constraints

When wrapping a Movable Point in a Transform, the point will be transformed too. However, your `constrain` function will be passed the untransformed point, and its return value will be transformed back into the currently applied transform. In other words, Mafs takes care of the math for you.

Let's see a more complex example where we combine more interesting constraint functions with transforms. On the left, we have a point that can only move in whole-number increments within a square, and on the right, a point that can only move in π/16 increments in a circle.

``````import { Mafs, Transform, Vector, Coordinates, useMovablePoint, Circle, Polygon, vec, Theme } from "mafs"
import clamp from "lodash/clamp"

function SnapPoint() {
return (
<Mafs viewBox={{ x: [-8, 8], y: [-2, 2] }}>
<Coordinates.Cartesian
xAxis={{ labels: false, axis: false }}
/>

<Transform translate={[-3, 0]}>
<Grid />
</Transform>

<Transform translate={[3, 0]}>
</Transform>
</Mafs>
)
}

function Grid() {
const gridMotion = useMovablePoint([1, 1], {
// Constrain this point to whole numbers inside of a rectangle
constrain: ([x, y]) => [
clamp(Math.round(x), -2, 2),
clamp(Math.round(y), -2, 2),
],
})

return (
<>
<Vector tail={[0, 0]} tip={gridMotion.point} />

<Polygon
points={[[-2, -2], [2, -2], [2, 2], [-2, 2]]}
color={Theme.blue}
/>
{gridMotion.element}
</>
)
}

// Constrain this point to specific angles from the center
constrain: (point) => {
const angle = Math.atan2(point[1], point[0])
const snap = Math.PI / 16
const roundedAngle = Math.round(angle / snap) * snap
},
})

return (
<>
<Circle
center={[0, 0]}
color={Theme.blue}
fillOpacity={0}
/>
</>
)
}``````

## the following section isAdvancedUsing MovablePoint directly

`useMovablePoint` is a hook that helps you instantiate and manage the state of a `MovablePoint`. However, if need be, you can also use `MovablePoint` directly. This can be useful if you need to work with a dynamic number of movable points (since the React "rules of hooks" ban you from dynamically calling hooks).

``````import { Mafs, Coordinates, MovablePoint, useMovablePoint, Line, Theme, vec } from "mafs"
import range from "lodash/range"

function DynamicMovablePoints() {
const start = useMovablePoint([-3, -1])
const end = useMovablePoint([3, 1])

function shift(shiftBy: vec.Vector2) {
}

const length = vec.dist(start.point, end.point)
const betweenPoints = range(1, length - 0.5, 1).map((t) =>
vec.lerp(start.point, end.point, t / length),
)

return (
<Mafs>
<Coordinates.Cartesian />

<Line.Segment
point1={start.point}
point2={end.point}
/>

{start.element}
{betweenPoints.map((point, i) => (
<MovablePoint
key={i}
point={point}
color={Theme.blue}
onMove={(newPoint) => {
shift(vec.sub(newPoint, point))
}}
/>
))}
{end.element}
</Mafs>
)
}``````

## Props

`<MovablePoint ... />`
View on GitHub
NameDescriptionDefault
`point*`

The current position `[x, y]` of the point.

`Vector2`
`onMove*`

A callback that is called as the user moves the point.

`(point: Vector2) => void`
`constrain`

Constrain the point to only horizontal movement, vertical movement, or mapped movement.

In mapped movement mode, you must provide a function that maps the user's mouse position `[x, y]` to the position the point should "snap" to.

`ConstraintFunction`
`(point) => point`
`color`
`string`
`var(--mafs-pink)`