Skip to main content

Vectors

Interactive vectors allow users to manipulate mathematical vectors by dragging their tail and tip points. This is useful for demonstrating vector concepts like magnitude, direction, and vector operations.

Interactive Vector

A vector with draggable tail (origin) and tip (arrow) points. Drag either endpoint to see the vector properties update in real-time.

import { InteractiveVector } from '@wangyaoshen/locus-interaction';

const vector = new InteractiveVector({
tail: [150, 200],
tip: [350, 100],
color: '#FF5722',
lineWidth: 4,
arrowSize: 18,
pointRadius: 10,
onChange: (tail, tip) => {
console.log('Vector changed:', tail, tip);
},
});

Vector Properties

The vector component provides several computed properties:

const vector = new InteractiveVector({
tail: [0, 0],
tip: [100, 100],
});

// Get magnitude (length)
const magnitude = vector.getMagnitude(); // 141.42...

// Get angle in radians
const angle = vector.getAngle(); // 0.785... (45 degrees)

// Get direction (unit vector)
const direction = vector.getDirection(); // [0.707..., 0.707...]

// Get the vector components
const vec = vector.getVector(); // [100, 100]

Constrained Endpoints

Apply constraints to the tail or tip points independently.

import {
InteractiveVector,
horizontal,
circle,
} from '@wangyaoshen/locus-interaction';

// Fixed tail at origin, tip constrained to circle
const vector = new InteractiveVector({
tail: [250, 150],
tip: [350, 150],
constrainTail: ([x, y]) => [250, 150], // Fixed origin
constrainTip: circle([250, 150], 100), // Tip on circle
onChange: (tail, tip) => {
const angle = vector.getAngle();
console.log('Angle:', (angle * 180) / Math.PI);
},
});

Set by Magnitude and Angle

You can set the vector using magnitude and angle instead of coordinates:

const vector = new InteractiveVector({
tail: [250, 150],
tip: [350, 150],
});

// Set magnitude to 100, angle to 45 degrees
vector.setMagnitudeAndAngle(100, Math.PI / 4);

// Now tip is approximately at [320.7, 79.3]

Props

NameTypeDefaultDescription
tailVector2[0, 0]Tail (origin) position
tipVector2[100, 0]Tip (arrow) position
colorstring'#FF5722'Vector color
lineWidthnumber4Line stroke width
arrowSizenumber18Arrowhead size
pointRadiusnumber10Tail point radius
showPointsbooleantrueShow tail point
constrainTailConstraintFunction-Tail constraint
constrainTipConstraintFunction-Tip constraint
onTailMove(pos) => void-Called when tail moves
onTipMove(pos) => void-Called when tip moves
onChange(tail, tip) => void-Called when vector changes

Using with Motion Canvas 2D

In Motion Canvas 2D, you can use the InteractiveVector2D component:

import { InteractiveVector2D } from '@wangyaoshen/locus-2d';
import { Vector2 } from '@wangyaoshen/locus-core';

// In your scene
<InteractiveVector2D
tail={new Vector2(0, 0)}
tip={new Vector2(100, 50)}
vectorColor="#FF5722"
lineWidth={3}
arrowSize={15}
pointRadius={8}
showPoints={true}
interactive={true}
onChange={(tail, tip) => console.log('Vector:', tail, tip)}
/>

InteractiveVector2D Props

NameTypeDefaultDescription
tailVector2(0, 0)Tail position
tipVector2(100, 0)Tip position
interactivebooleanfalseEnable interaction
vectorColorstring'#FF5722'Vector color
arrowSizenumber15Arrowhead size
pointRadiusnumber8Tail point radius
showPointsbooleantrueShow endpoints
constrainTailConstraintFunction-Tail constraint
constrainTipConstraintFunction-Tip constraint
onTailMove(pos) => void-Tail move callback
onTipMove(pos) => void-Tip move callback
onChange(tail, tip) => void-Change callback

Use Cases

Magnitude and Direction Display

const vector = new InteractiveVector({
tail: [250, 150],
tip: [350, 100],
onChange: (tail, tip) => {
const mag = vector.getMagnitude();
const angle = vector.getAngle();
const degrees = (angle * 180) / Math.PI;

document.getElementById('info').textContent =
`Magnitude: ${mag.toFixed(1)} | Angle: ${degrees.toFixed(1)}°`;
},
});

Force Diagram

// Create multiple vectors from a common origin
const origin = [250, 200];

const force1 = new InteractiveVector({
tail: origin,
tip: [350, 150],
color: '#F44336',
constrainTail: ([x, y]) => origin,
});

const force2 = new InteractiveVector({
tail: origin,
tip: [150, 100],
color: '#2196F3',
constrainTail: ([x, y]) => origin,
});

// Calculate resultant vector
function getResultant() {
const v1 = force1.getVector();
const v2 = force2.getVector();
return [v1[0] + v2[0], v1[1] + v2[1]];
}

Unit Circle Demo

const vector = new InteractiveVector({
tail: [250, 200],
tip: [350, 200],
constrainTail: ([x, y]) => [250, 200],
constrainTip: circle([250, 200], 100),
onChange: (tail, tip) => {
const angle = vector.getAngle();
const degrees = (angle * 180) / Math.PI;

// Show trigonometric values
document.getElementById('cos').textContent =
`cos(${degrees.toFixed(0)}°) = ${Math.cos(angle).toFixed(3)}`;
document.getElementById('sin').textContent =
`sin(${degrees.toFixed(0)}°) = ${Math.sin(angle).toFixed(3)}`;
},
});

Vector Addition

const vectorA = new InteractiveVector({
tail: [100, 200],
tip: [200, 100],
color: '#F44336',
});

const vectorB = new InteractiveVector({
tail: [200, 100], // Starts where A ends
tip: [350, 150],
color: '#2196F3',
});

// Resultant vector (A + B)
const resultant = new InteractiveVector({
tail: [100, 200],
tip: [350, 150],
color: '#4CAF50',
interactive: false, // Not draggable
});

// Update resultant when A or B changes
function updateResultant() {
resultant.setTail(vectorA.getTail());
resultant.setTip(vectorB.getTip());

// Update B's tail to match A's tip
vectorB.setTail(vectorA.getTip());
}

vectorA.onChange = updateResultant;
vectorB.onChange = updateResultant;