|
| 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