Skip to content

Commit fc54c92

Browse files
committed
Add initial version of vector addition demo
1 parent e693287 commit fc54c92

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

interactive/vector-addition.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Vector Sum Plotter</title>
7+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
8+
<style>
9+
#plot {
10+
width: 100%;
11+
height: 600px;
12+
}
13+
</style>
14+
</head>
15+
<body>
16+
<h1>Vector Plotter</h1>
17+
<p>Enter a sequence of 2D vectors in the format:</p>
18+
<p>[x1, y1]<br>[x2, y2]<br>&hellip;</p>
19+
<p>The plot will show how to find the sum of the vectors by concatenating vectors such that the tail of each vector is at the head of the previous vector. The orange, dashed vector represents the resulting sum of all entered vectors.</p>
20+
<textarea id="vector-plot" rows="10" cols="50" placeholder="Enter vectors here"></textarea>
21+
<div id="plot"></div>
22+
23+
<script src="vector-addition.js"></script>
24+
</body>
25+
</html>

interactive/vector-addition.js

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
document.getElementById('vector-plot').addEventListener('input', plotVectors);
2+
3+
document.getElementById('vector-plot').addEventListener('keydown', function(event) {
4+
const allowedKeys = [
5+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // Number keys
6+
',', '[', ']', ' ', // Comma, left bracket, right bracket, space
7+
'-', '+', // Signs
8+
'.','e', 'E', // Decimal point and scientific notation
9+
' ', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', // Arrow keys and space
10+
'Enter', 'Backspace', 'Delete' // Enter, Backspace, and Delete
11+
];
12+
13+
const key = event.key;
14+
15+
if (!allowedKeys.includes(key)) {
16+
event.preventDefault();
17+
return;
18+
}
19+
20+
const textarea = event.target;
21+
22+
if (key === '[') {
23+
const cursorPosition = textarea.selectionStart;
24+
const valueUpToCursor = textarea.value.substring(0, cursorPosition);
25+
const lastLineBreak = valueUpToCursor.lastIndexOf('\n');
26+
const currentLine = valueUpToCursor.substring(lastLineBreak + 1);
27+
if (currentLine.includes('[')) {
28+
event.preventDefault();
29+
return;
30+
}
31+
}
32+
33+
if (key === ']') {
34+
const cursorPosition = textarea.selectionStart;
35+
event.preventDefault();
36+
textarea.value = textarea.value.substring(0, cursorPosition) + ']' +
37+
textarea.value.substring(cursorPosition);
38+
textarea.selectionStart = textarea.selectionEnd = cursorPosition + 1;
39+
plotVectors();
40+
}
41+
});
42+
43+
let x_min = -5, x_max = 5, y_min = -5, y_max = 5;
44+
45+
function plotVectors() {
46+
const colors = ['#345995', '#e26d5a', '#87a878', '#5bc0eb', '#861657'];
47+
const input = document.getElementById('vector-plot').value;
48+
const vectors = parseVectors(input);
49+
50+
let currentTail = [0, 0];
51+
const traces = [];
52+
var annotations = [];
53+
const annotations2 = [];
54+
vectors.forEach((vec, index) => {
55+
const vectorTrace = {
56+
x: [currentTail[0], currentTail[0] + vec[0]],
57+
y: [currentTail[1], currentTail[1] + vec[1]],
58+
//mode: 'lines+markers+text',
59+
mode: 'lines',
60+
text: `[${vec[0]}, ${vec[1]}]`,
61+
// textposition: 'top',
62+
name: String(vec),
63+
line: {
64+
width: 2,
65+
color: colors[index % colors.length]
66+
},
67+
marker: {
68+
size: 6
69+
}
70+
};
71+
traces.push(vectorTrace);
72+
annotations.push({
73+
ax: currentTail[0],
74+
ay: currentTail[1],
75+
axref: 'x',
76+
ayref: 'y',
77+
x: currentTail[0] + vec[0],
78+
y: currentTail[1] + vec[1],
79+
xref: 'x',
80+
yref: 'y',
81+
showarrow: true,
82+
arrowhead: 2,
83+
arrowsize: 1,
84+
arrowwidth: 4,
85+
arrowcolor: colors[index % colors.length]
86+
});
87+
88+
annotations2.push ({
89+
x: currentTail[0] + vec[0] + Math.sign(vec[0])*0.2,
90+
y: currentTail[1] + vec[1] + Math.sign(vec[1])*0.2,
91+
xref: 'x',
92+
yref: 'y',
93+
showarrow: false,
94+
text: `[${vec[0]}, ${vec[1]}]`,
95+
offset:0.2,
96+
font: {
97+
color: colors[index % colors.length],
98+
size: 14
99+
},
100+
});
101+
102+
currentTail = [currentTail[0] + vec[0], currentTail[1] + vec[1]];
103+
});
104+
105+
annotations = annotations.concat(annotations2);
106+
const vectorSum = vectors.reduce((acc, vec) => [acc[0] + vec[0], acc[1] + vec[1]], [0, 0]);
107+
108+
if (vectors.length > 0) {
109+
const x_values = vectors.map(vec => vec[0]);
110+
const y_values = vectors.map(vec => vec[1]);
111+
112+
const new_x_min = Math.min(0, vectorSum[0], ...x_values) - 1;
113+
const new_x_max = Math.max(0, vectorSum[0], ...x_values) + 1;
114+
const new_y_min = Math.min(0, vectorSum[1], ...y_values) - 1;
115+
const new_y_max = Math.max(0, vectorSum[1], ...y_values) + 1;
116+
117+
if (new_x_min < x_min) x_min = new_x_min;
118+
if (new_x_max > x_max) x_max = new_x_max;
119+
if (new_y_min < y_min) y_min = new_y_min;
120+
if (new_y_max > y_max) y_max = new_y_max;
121+
}
122+
123+
var overallMax = Math.max( x_max - x_min , y_max - y_min);
124+
125+
if (vectors.length >= 2) {
126+
const vectorSumTrace = {
127+
x: [0, vectorSum[0]],
128+
y: [0, vectorSum[1]],
129+
mode: 'lines',
130+
line: {
131+
width: 4,
132+
color: 'orange',
133+
dash: 'dash'
134+
},
135+
name: `Sum: [${vectorSum[0]}, ${vectorSum[1]}]`
136+
};
137+
traces.push(vectorSumTrace);
138+
const vecLen = Math.sqrt(vectorSum[0]**2 + vectorSum[1]**2);
139+
const vectorSumAnnotation = {
140+
ax: vectorSum[0] - 0.05*overallMax*vectorSum[0]/vecLen,
141+
ay: vectorSum[1] - 0.05*overallMax*vectorSum[1]/vecLen,
142+
//ax: vectorSum[0] - 0.2*vectorSum[0]/vecLen,
143+
//ay: vectorSum[1] - 0.2*vectorSum[1]/vecLen,
144+
axref: 'x',
145+
ayref: 'y',
146+
x: vectorSum[0],
147+
y: vectorSum[1],
148+
xref: 'x',
149+
yref: 'y',
150+
showarrow: true,
151+
arrowhead: 2,
152+
arrowsize: 2,
153+
arrowwidth: 2,
154+
arrowcolor: 'orange'
155+
};
156+
annotations.push(vectorSumAnnotation);
157+
var xOffset;
158+
if (vectorSum[0] >0) {
159+
xOffset = 1;
160+
}
161+
else{
162+
xOffset= -1;
163+
}
164+
165+
var yOffset;
166+
if (vectorSum[1] >0) {
167+
yOffset = 2;
168+
}
169+
else{
170+
yOffset= -2;
171+
}
172+
173+
annotations.push ({
174+
x: 0.45*vectorSum[0] + xOffset,
175+
y: 0.45*vectorSum[1] + yOffset,
176+
xref: 'x',
177+
yref: 'y',
178+
showarrow: false,
179+
text: `sum = [${vectorSum[0]}, ${vectorSum[1]}]`,
180+
offset:0.2,
181+
font: {
182+
color: 'orange',
183+
size: 14
184+
},
185+
});
186+
}
187+
188+
189+
if (vectors.length > 0) {
190+
const x_values = vectors.map(vec => vec[0]);
191+
const y_values = vectors.map(vec => vec[1]);
192+
193+
const new_x_min = Math.min(0, vectorSum[0], ...x_values) - 1;
194+
const new_x_max = Math.max(0, vectorSum[0], ...x_values) + 1;
195+
const new_y_min = Math.min(0, ...y_values) - 1;
196+
const new_y_max = Math.max(0, ...y_values) + 1;
197+
198+
if (new_x_min < x_min) x_min = new_x_min;
199+
if (new_x_max > x_max) x_max = new_x_max;
200+
if (new_y_min < y_min) y_min = new_y_min;
201+
if (new_y_max > y_max) y_max = new_y_max;
202+
}
203+
204+
const layout = {
205+
xaxis: { range: [x_min, x_max] },
206+
yaxis: { range: [y_min, y_max] },
207+
showlegend: false,
208+
annotations: annotations
209+
};
210+
211+
Plotly.newPlot('plot', traces, layout);
212+
}
213+
214+
function parseVectors(input) {
215+
const lines = input.split('\n');
216+
const vectors = [];
217+
for (let line of lines) {
218+
line = line.trim();
219+
if (line.length > 0) {
220+
try {
221+
const vector = JSON.parse(line.replace(/\+/g, ''))
222+
.map(Number)
223+
.filter(num => !isNaN(num) && isFinite(num))
224+
.slice(0, 2);
225+
if (Array.isArray(vector) && vector.length == 2) {
226+
vectors.push(vector);
227+
}
228+
} catch (e) {
229+
console.error("Invalid input: ", e);
230+
}
231+
}
232+
}
233+
return vectors;
234+
}
235+
236+
plotVectors(); // Initial call to plot any default vectors

0 commit comments

Comments
 (0)