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
| Name | Type | Default | Description |
|---|---|---|---|
tail | Vector2 | [0, 0] | Tail (origin) position |
tip | Vector2 | [100, 0] | Tip (arrow) position |
color | string | '#FF5722' | Vector color |
lineWidth | number | 4 | Line stroke width |
arrowSize | number | 18 | Arrowhead size |
pointRadius | number | 10 | Tail point radius |
showPoints | boolean | true | Show tail point |
constrainTail | ConstraintFunction | - | Tail constraint |
constrainTip | ConstraintFunction | - | 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
| Name | Type | Default | Description |
|---|---|---|---|
tail | Vector2 | (0, 0) | Tail position |
tip | Vector2 | (100, 0) | Tip position |
interactive | boolean | false | Enable interaction |
vectorColor | string | '#FF5722' | Vector color |
arrowSize | number | 15 | Arrowhead size |
pointRadius | number | 8 | Tail point radius |
showPoints | boolean | true | Show endpoints |
constrainTail | ConstraintFunction | - | Tail constraint |
constrainTip | ConstraintFunction | - | 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;