643 lines
23 KiB
HTML
643 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Distanzmetriken Visualisierung</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
margin-bottom: 40px;
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
h1 {
|
||
color: #333;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.subtitle {
|
||
color: #666;
|
||
}
|
||
|
||
.highlight-box {
|
||
background: #fff3cd;
|
||
border-left: 4px solid #ffc107;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.highlight-box h3 {
|
||
color: #856404;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.highlight-box p {
|
||
color: #856404;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.controls {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.controls h2 {
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.control-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 40px;
|
||
}
|
||
|
||
.vector-controls {
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.vector-controls.v1 {
|
||
background: #e3f2fd;
|
||
border-left: 4px solid #2196F3;
|
||
}
|
||
|
||
.vector-controls.v2 {
|
||
background: #ffebee;
|
||
border-left: 4px solid #f44336;
|
||
}
|
||
|
||
.vector-controls h3 {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.control-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.control-group label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
color: #555;
|
||
}
|
||
|
||
input[type="range"] {
|
||
width: 100%;
|
||
height: 6px;
|
||
border-radius: 3px;
|
||
background: #ddd;
|
||
outline: none;
|
||
}
|
||
|
||
input[type="range"]::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: #2196F3;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.viz-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 30px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.viz-card {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.viz-card h3 {
|
||
margin-bottom: 20px;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.viz-card.euclidean h3 {
|
||
color: #9c27b0;
|
||
}
|
||
|
||
.viz-card.cosine h3 {
|
||
color: #4caf50;
|
||
}
|
||
|
||
.svg-container {
|
||
background: #fafafa;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 8px;
|
||
padding: 10px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.result-box {
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.result-box.euclidean {
|
||
background: #f3e5f5;
|
||
border-left: 4px solid #9c27b0;
|
||
}
|
||
|
||
.result-box.cosine {
|
||
background: #e8f5e9;
|
||
border-left: 4px solid #4caf50;
|
||
}
|
||
|
||
.formula {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 14px;
|
||
margin-bottom: 10px;
|
||
padding: 10px;
|
||
background: rgba(0,0,0,0.05);
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.result-value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.result-box.euclidean .result-value {
|
||
color: #9c27b0;
|
||
}
|
||
|
||
.result-box.cosine .result-value {
|
||
color: #4caf50;
|
||
}
|
||
|
||
.interpretation {
|
||
margin-top: 15px;
|
||
padding: 15px;
|
||
background: white;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.interpretation.similar {
|
||
color: #2e7d32;
|
||
border: 2px solid #4caf50;
|
||
}
|
||
|
||
.interpretation.dissimilar {
|
||
color: #c62828;
|
||
border: 2px solid #f44336;
|
||
}
|
||
|
||
.interpretation.medium {
|
||
color: #f57c00;
|
||
border: 2px solid #ff9800;
|
||
}
|
||
|
||
.description {
|
||
font-size: 14px;
|
||
color: #666;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.comparison {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.comparison h2 {
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.comparison-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 30px;
|
||
}
|
||
|
||
.comparison-box {
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.comparison-box.euclidean {
|
||
background: #f3e5f5;
|
||
border-left: 4px solid #9c27b0;
|
||
}
|
||
|
||
.comparison-box.cosine {
|
||
background: #e8f5e9;
|
||
border-left: 4px solid #4caf50;
|
||
}
|
||
|
||
.comparison-box h3 {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.comparison-box ul {
|
||
list-style: none;
|
||
padding-left: 0;
|
||
}
|
||
|
||
.comparison-box li {
|
||
padding: 5px 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.quick-test {
|
||
background: #e3f2fd;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
margin-bottom: 30px;
|
||
border-left: 4px solid #2196F3;
|
||
}
|
||
|
||
.quick-test h3 {
|
||
color: #1565c0;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.test-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.test-btn {
|
||
padding: 10px 20px;
|
||
background: #2196F3;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.test-btn:hover {
|
||
background: #1976d2;
|
||
}
|
||
|
||
@media (max-width: 968px) {
|
||
.control-grid, .viz-grid, .comparison-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>Distanzmetriken für Vektorvergleiche</h1>
|
||
<p class="subtitle">Interaktive Visualisierung der mathematischen Konzepte</p>
|
||
</div>
|
||
|
||
<div class="highlight-box">
|
||
<h3>Der entscheidende Unterschied!</h3>
|
||
<p><strong>Teste die Szenarien unten:</strong> Wenn beide Vektoren die gleiche Richtung haben (gleicher Winkel),
|
||
aber unterschiedlich lang sind, zeigt Kosinus perfekte Ähnlichkeit (1.0), während Euklidisch eine große Distanz (= wenig bis mittlere Ähnlichkeit) gemessen wird.</p>
|
||
</div>
|
||
|
||
<div class="quick-test">
|
||
<h3>Schnell-Test:</h3>
|
||
<div class="test-buttons">
|
||
<button class="test-btn" onclick="testScenario('same-direction')">
|
||
Gleiche Richtung, unterschiedliche Länge
|
||
</button>
|
||
<button class="test-btn" onclick="testScenario('different-direction')">
|
||
Unterschiedliche Richtung, gleiche Länge
|
||
</button>
|
||
<button class="test-btn" onclick="testScenario('orthogonal')">
|
||
Orthogonal (90° Unterschied)
|
||
</button>
|
||
<button class="test-btn" onclick="testScenario('opposite')">
|
||
Fast entgegengesetzt
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<h2>Vektoreinstellungen</h2>
|
||
<div class="control-grid">
|
||
<div class="vector-controls v1">
|
||
<h3>Vektor 1 (Blau)</h3>
|
||
<div class="control-group">
|
||
<label>Winkel: <span id="angle1-value">30</span>°</label>
|
||
<input type="range" id="angle1" min="0" max="180" value="30">
|
||
</div>
|
||
<div class="control-group">
|
||
<label>Länge: <span id="length1-value">80</span></label>
|
||
<input type="range" id="length1" min="20" max="100" value="80">
|
||
</div>
|
||
</div>
|
||
<div class="vector-controls v2">
|
||
<h3>Vektor 2 (Rot)</h3>
|
||
<div class="control-group">
|
||
<label>Winkel: <span id="angle2-value">60</span>°</label>
|
||
<input type="range" id="angle2" min="0" max="180" value="60">
|
||
</div>
|
||
<div class="control-group">
|
||
<label>Länge: <span id="length2-value">60</span></label>
|
||
<input type="range" id="length2" min="20" max="100" value="60">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="viz-grid">
|
||
<div class="viz-card euclidean">
|
||
<h3>Euklidische Distanz</h3>
|
||
<div class="svg-container">
|
||
<svg id="euclidean-svg" width="100%" height="300" viewBox="-20 -220 260 260"></svg>
|
||
</div>
|
||
<div class="result-box euclidean">
|
||
<div class="formula">d = √[(x₂-x₁)² + (y₂-y₁)²]</div>
|
||
<div class="result-value">Distanz: <span id="euclidean-result">0.00</span></div>
|
||
<div class="interpretation" id="euclidean-interpretation"></div>
|
||
<div class="description">
|
||
Misst die <strong>direkte räumliche Entfernung</strong> zwischen den Vektorendpunkten.
|
||
Berücksichtigt sowohl Richtung als auch Länge der Vektoren.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="viz-card cosine">
|
||
<h3>Kosinus-Ähnlichkeit</h3>
|
||
<div class="svg-container">
|
||
<svg id="cosine-svg" width="100%" height="300" viewBox="-20 -220 260 260"></svg>
|
||
</div>
|
||
<div class="result-box cosine">
|
||
<div class="formula">cos(θ) = (v1 · v2) / (||v1|| × ||v2||)</div>
|
||
<div class="result-value">Ähnlichkeit: <span id="cosine-result">0.0000</span></div>
|
||
<div class="interpretation" id="cosine-interpretation"></div>
|
||
<div class="description">
|
||
Misst nur den <strong>Winkel zwischen Vektoren</strong>, ignoriert die Länge komplett!
|
||
Perfekt für Text-Embeddings: "Hund" und "großer Hund" haben gleiche Semantik (Richtung),
|
||
auch wenn unterschiedlich lang.
|
||
</div>
|
||
<div class="description" style="margin-top: 10px; font-size: 12px;">
|
||
1.0 = gleiche Richtung | 0.0 = orthogonal (90°) | -1.0 = entgegengesetzt (180°)
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="comparison">
|
||
<h2>Wann welche Metrik verwenden?</h2>
|
||
<div class="comparison-grid">
|
||
<div class="comparison-box euclidean">
|
||
<h3>Euklidische Distanz</h3>
|
||
<ul>
|
||
<li>✓ Geografische Koordinaten</li>
|
||
<li>✓ Physikalische Messungen</li>
|
||
<li>✓ Pixelwerte in Bildern</li>
|
||
<li>✓ Wenn absolute Position wichtig ist</li>
|
||
<li>✓ Wenn Magnitude (Länge) relevant ist</li>
|
||
<li>✗ Nicht ideal für hochdimensionale Daten</li>
|
||
</ul>
|
||
</div>
|
||
<div class="comparison-box cosine">
|
||
<h3>Kosinus-Ähnlichkeit</h3>
|
||
<ul>
|
||
<li>✓ Text-Embeddings (Semantik zählt, nicht Länge)</li>
|
||
<li>✓ Dokumentenähnlichkeit</li>
|
||
<li>✓ Empfehlungssysteme</li>
|
||
<li>✓ Wenn nur Richtung wichtig ist</li>
|
||
<li>✓ Robust gegenüber Skalierung</li>
|
||
<li>✓ Ideal für normalisierte Daten</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const scale = 2;
|
||
|
||
function toRadians(deg) {
|
||
return deg * Math.PI / 180;
|
||
}
|
||
|
||
function testScenario(scenario) {
|
||
const scenarios = {
|
||
'same-direction': { a1: 45, l1: 90, a2: 45, l2: 30 },
|
||
'different-direction': { a1: 30, l1: 70, a2: 90, l2: 70 },
|
||
'orthogonal': { a1: 0, l1: 70, a2: 90, l2: 70 },
|
||
'opposite': { a1: 20, l1: 70, a2: 160, l2: 70 }
|
||
};
|
||
|
||
const s = scenarios[scenario];
|
||
document.getElementById('angle1').value = s.a1;
|
||
document.getElementById('length1').value = s.l1;
|
||
document.getElementById('angle2').value = s.a2;
|
||
document.getElementById('length2').value = s.l2;
|
||
update();
|
||
}
|
||
|
||
function getEuclideanInterpretation(dist, maxDist = 200) {
|
||
const normalized = dist / maxDist;
|
||
if (normalized < 0.2) {
|
||
return { class: 'similar', text: '✓ Sehr ähnlich (kleine Distanz)' };
|
||
} else if (normalized < 0.5) {
|
||
return { class: 'medium', text: '~ Mittlere Ähnlichkeit' };
|
||
} else {
|
||
return { class: 'dissimilar', text: '✗ Sehr unterschiedlich (große Distanz)' };
|
||
}
|
||
}
|
||
|
||
function getCosineInterpretation(similarity) {
|
||
if (similarity > 0.9) {
|
||
return { class: 'similar', text: '✓ Sehr ähnlich (fast gleiche Richtung)' };
|
||
} else if (similarity > 0.5) {
|
||
return { class: 'medium', text: '~ Mittlere Ähnlichkeit' };
|
||
} else if (similarity > 0) {
|
||
return { class: 'dissimilar', text: '✗ Unterschiedlich (großer Winkel)' };
|
||
} else if (similarity > -0.5) {
|
||
return { class: 'dissimilar', text: '✗ Sehr unterschiedlich (> 90°)' };
|
||
} else {
|
||
return { class: 'dissimilar', text: '✗✗ Fast entgegengesetzt!' };
|
||
}
|
||
}
|
||
|
||
function drawEuclideanViz(v1x, v1y, v2x, v2y) {
|
||
const svg = document.getElementById('euclidean-svg');
|
||
svg.innerHTML = `
|
||
<!-- Koordinatensystem -->
|
||
<line x1="0" y1="0" x2="220" y2="0" stroke="#ddd" stroke-width="1" />
|
||
<line x1="0" y1="0" x2="0" y2="-220" stroke="#ddd" stroke-width="1" />
|
||
|
||
<!-- Gitter -->
|
||
${[50, 100, 150, 200].map(i => `
|
||
<line x1="${i}" y1="0" x2="${i}" y2="-200" stroke="#f0f0f0" stroke-width="0.5" />
|
||
<line x1="0" y1="${-i}" x2="200" y2="${-i}" stroke="#f0f0f0" stroke-width="0.5" />
|
||
`).join('')}
|
||
|
||
<!-- Distanzlinie -->
|
||
<line x1="${v1x * scale}" y1="${-v1y * scale}"
|
||
x2="${v2x * scale}" y2="${-v2y * scale}"
|
||
stroke="purple" stroke-width="3" stroke-dasharray="5,5" />
|
||
|
||
<!-- Vektor 1 -->
|
||
<defs>
|
||
<marker id="arrowBlue" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||
<path d="M0,0 L0,6 L9,3 z" fill="blue" />
|
||
</marker>
|
||
<marker id="arrowRed" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||
<path d="M0,0 L0,6 L9,3 z" fill="red" />
|
||
</marker>
|
||
</defs>
|
||
<line x1="0" y1="0" x2="${v1x * scale}" y2="${-v1y * scale}"
|
||
stroke="blue" stroke-width="3" marker-end="url(#arrowBlue)" />
|
||
|
||
<!-- Vektor 2 -->
|
||
<line x1="0" y1="0" x2="${v2x * scale}" y2="${-v2y * scale}"
|
||
stroke="red" stroke-width="3" marker-end="url(#arrowRed)" />
|
||
|
||
<!-- Endpunkte -->
|
||
<circle cx="${v1x * scale}" cy="${-v1y * scale}" r="4" fill="blue" />
|
||
<circle cx="${v2x * scale}" cy="${-v2y * scale}" r="4" fill="red" />
|
||
|
||
<!-- Labels -->
|
||
<text x="${v1x * scale + 10}" y="${-v1y * scale}" fill="blue" font-size="14" font-weight="bold">v1</text>
|
||
<text x="${v2x * scale + 10}" y="${-v2y * scale}" fill="red" font-size="14" font-weight="bold">v2</text>
|
||
<text x="${(v1x + v2x) * scale / 2}" y="${-(v1y + v2y) * scale / 2 - 10}" fill="purple" font-size="12" font-weight="bold">d</text>
|
||
`;
|
||
}
|
||
|
||
function drawCosineViz(v1x, v1y, v2x, v2y, angle1, angle2) {
|
||
const svg = document.getElementById('cosine-svg');
|
||
const angleDiff = Math.abs(angle2 - angle1);
|
||
|
||
// Winkelbogen berechnen
|
||
const arcRadius = 30;
|
||
const startAngle = toRadians(Math.min(angle1, angle2));
|
||
const endAngle = toRadians(Math.max(angle1, angle2));
|
||
const largeArc = angleDiff > 180 ? 1 : 0;
|
||
const sweep = 1;
|
||
|
||
svg.innerHTML = `
|
||
<!-- Koordinatensystem -->
|
||
<line x1="0" y1="0" x2="220" y2="0" stroke="#ddd" stroke-width="1" />
|
||
<line x1="0" y1="0" x2="0" y2="-220" stroke="#ddd" stroke-width="1" />
|
||
|
||
<!-- Gitter -->
|
||
${[50, 100, 150, 200].map(i => `
|
||
<line x1="${i}" y1="0" x2="${i}" y2="-200" stroke="#f0f0f0" stroke-width="0.5" />
|
||
<line x1="0" y1="${-i}" x2="200" y2="${-i}" stroke="#f0f0f0" stroke-width="0.5" />
|
||
`).join('')}
|
||
|
||
<!-- Winkelbogen -->
|
||
<path d="M ${arcRadius * Math.cos(startAngle)} ${-arcRadius * Math.sin(startAngle)}
|
||
A ${arcRadius} ${arcRadius} 0 ${largeArc} ${sweep}
|
||
${arcRadius * Math.cos(endAngle)} ${-arcRadius * Math.sin(endAngle)}"
|
||
fill="none" stroke="green" stroke-width="2" />
|
||
|
||
<!-- Vektoren -->
|
||
<defs>
|
||
<marker id="arrowBlue2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||
<path d="M0,0 L0,6 L9,3 z" fill="blue" />
|
||
</marker>
|
||
<marker id="arrowRed2" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto">
|
||
<path d="M0,0 L0,6 L9,3 z" fill="red" />
|
||
</marker>
|
||
</defs>
|
||
<line x1="0" y1="0" x2="${v1x * scale}" y2="${-v1y * scale}"
|
||
stroke="blue" stroke-width="3" marker-end="url(#arrowBlue2)" />
|
||
<line x1="0" y1="0" x2="${v2x * scale}" y2="${-v2y * scale}"
|
||
stroke="red" stroke-width="3" marker-end="url(#arrowRed2)" />
|
||
|
||
<!-- Labels -->
|
||
<text x="${v1x * scale + 10}" y="${-v1y * scale}" fill="blue" font-size="14" font-weight="bold">v1</text>
|
||
<text x="${v2x * scale + 10}" y="${-v2y * scale}" fill="red" font-size="14" font-weight="bold">v2</text>
|
||
<text x="45" y="-45" fill="green" font-size="12" font-weight="bold">θ = ${angleDiff.toFixed(1)}°</text>
|
||
`;
|
||
}
|
||
|
||
function update() {
|
||
const angle1 = parseFloat(document.getElementById('angle1').value);
|
||
const length1 = parseFloat(document.getElementById('length1').value);
|
||
const angle2 = parseFloat(document.getElementById('angle2').value);
|
||
const length2 = parseFloat(document.getElementById('length2').value);
|
||
|
||
// Update labels
|
||
document.getElementById('angle1-value').textContent = angle1;
|
||
document.getElementById('length1-value').textContent = length1;
|
||
document.getElementById('angle2-value').textContent = angle2;
|
||
document.getElementById('length2-value').textContent = length2;
|
||
|
||
// Berechne kartesische Koordinaten
|
||
const v1x = length1 * Math.cos(toRadians(angle1));
|
||
const v1y = length1 * Math.sin(toRadians(angle1));
|
||
const v2x = length2 * Math.cos(toRadians(angle2));
|
||
const v2y = length2 * Math.sin(toRadians(angle2));
|
||
|
||
// Euklidische Distanz
|
||
const euclidean = Math.sqrt(Math.pow(v2x - v1x, 2) + Math.pow(v2y - v1y, 2));
|
||
document.getElementById('euclidean-result').textContent = euclidean.toFixed(2);
|
||
|
||
const eucInterpret = getEuclideanInterpretation(euclidean);
|
||
const eucInterpretEl = document.getElementById('euclidean-interpretation');
|
||
eucInterpretEl.textContent = eucInterpret.text;
|
||
eucInterpretEl.className = 'interpretation ' + eucInterpret.class;
|
||
|
||
// Kosinus-Ähnlichkeit
|
||
const dotProduct = v1x * v2x + v1y * v2y;
|
||
const norm1 = Math.sqrt(v1x * v1x + v1y * v1y);
|
||
const norm2 = Math.sqrt(v2x * v2x + v2y * v2y);
|
||
const cosine = dotProduct / (norm1 * norm2);
|
||
document.getElementById('cosine-result').textContent = cosine.toFixed(4);
|
||
|
||
const cosInterpret = getCosineInterpretation(cosine);
|
||
const cosInterpretEl = document.getElementById('cosine-interpretation');
|
||
cosInterpretEl.textContent = cosInterpret.text;
|
||
cosInterpretEl.className = 'interpretation ' + cosInterpret.class;
|
||
|
||
// Zeichne Visualisierungen
|
||
drawEuclideanViz(v1x, v1y, v2x, v2y);
|
||
drawCosineViz(v1x, v1y, v2x, v2y, angle1, angle2);
|
||
}
|
||
|
||
// Event Listeners
|
||
document.getElementById('angle1').addEventListener('input', update);
|
||
document.getElementById('length1').addEventListener('input', update);
|
||
document.getElementById('angle2').addEventListener('input', update);
|
||
document.getElementById('length2').addEventListener('input', update);
|
||
|
||
// Initial draw
|
||
update();
|
||
</script>
|
||
</body>
|
||
</html> |